(Art by shahabalizadeh)
For devs who love ergonomics! You may appreciate Surreal if:
- You want to stay as close as possible to Vanilla JS.
- Hate typing
document.querySelector
over.. and over.. - Hate typing
addEventListener
over.. and over.. - Really wish
document.querySelectorAll
had Array functions.. - Really wish
this
would work in any inline<script>
tag - Enjoyed using jQuery selector syntax.
- Animations, timelines, tweens with no extra libraries.
- Only 320 lines. No build step. No dependencies.
- Pairs well with htmx
- Want fewer layers, less complexity. Are aware of the cargo cult.
โ๏ธ
- โก๏ธ Locality of Behavior (LoB) Use
me()
inside<script>
- Get an element without creating a unique name: No .class or #id needed!
this
but better!- Want
me
in your CSS<style>
tags, too? See our companion script
- ๐ Call chaining, jQuery style.
- โป๏ธ Functions work seamlessly on 1 element or arrays of elements!
- All functions can use:
me()
,any()
,NodeList
,HTMLElement
(..or arrays of these!) - Get 1 element:
me()
- ..or many elements:
any()
me()
orany()
can chain with any Surreal function.me()
can be used directly as a single element (likequerySelector()
or$()
)any()
can use:for
/forEach
/filter
/map
(likequerySelectorAll()
or$()
)
- All functions can use:
- ๐ No forced style. Use:
classAdd
orclass_add
oraddClass
oradd_class
- Use
camelCase
(Javascript) orsnake_case
(Python, Rust, PHP, Ruby, SQL, CSS).
- Use
- ๐ก We solve the classic jQuery code bloat problem: Am I getting 1 element or an array of elements?
me()
is guaranteed to return 1 element (or first found, or null).any()
is guaranteed to return an array (or empty array).- No more checks = you write less code. Bonus: Code reads more like self-documenting english.
Do surreal things with Locality of Behavior like:
<label for="file-input" >
<div class="uploader"></div>
<script>
me().on("dragover", ev => { halt(ev); me(ev).classAdd('.hover'); console.log("Files in drop zone.") })
me().on("dragleave", ev => { halt(ev); me(ev).classAdd('.hover'); console.log("Files left drop zone.") })
me().on("drop", ev => { halt(ev); me(ev).classRemove('.hover').classAdd('.loading'); me('#file-input').attribute('files', ev.dataTransfer.files); me('#form').trigger('change') })
</script>
</label>
See the Live Example! Then view source.
Surreal is only 320 lines. No build step. No dependencies.
๐ฅ Download into your project, and add <script src="/surreal.js"></script>
in your <head>
Or, ๐ use the CDN: <script src="https://cdn.jsdelivr.net/gh/gnat/surreal/surreal.js"></script>
- Select one element:
me(...)
- Can be any of:
- CSS selector:
".button"
,"#header"
,"h1"
,"body > .block"
- Variables:
body
,e
,some_element
- Events:
event.target
will be used. - Surreal selectors:
me()
,any()
- Adding a
start=
parameter provides a starting DOM location to select from. Default isdocument
โถ๏ธ any('button', start='header').classAdd('red')
- CSS selector:
me()
Get current element for Locality of Behavior in<script>
without an explicit .class or #idme("body")
Gets<body>
me(".button")
Gets the first<div class="button">...</div>
. To get all of them useany()
- Can be any of:
- Select one or more elements as an array:
any(...)
- Similar to
me()
but guaranteed to return an array (or empty array). any(".foo")
Gets all matching elements, such as:<div class="foo">...</div>
- Feel free to convert between arrays of elements and single elements:
any(me())
,me(any(".something"))
- Similar to
- โป๏ธ All functions work on single elements or arrays of elements.
- ๐ Start a chain using
me()
andany()
- ๐ข Style A
me().classAdd('red')
โญ Chain style, recommended! - ๐ Style B:
classAdd(me(), 'red')
- ๐ข Style A
- ๐ Global conveniences help you write less code.
globalsAdd()
will automatically warn about any clobbering issues.- If you prefer no conveniences, just delete
globalsAdd()
me().classAdd('red')
withglobalsAdd()
removed:$.me().classAdd('red')
classAdd(me(), 'red')
withglobalsAdd()
removed:$.classAdd($.me(), 'red')
- If you prefer no conveniences, just delete
See: Quick Start and Reference and No Surreal Needed
- Add a class
me().classAdd('red')
any("button").classAdd('red')
- Events
me().on("click", ev => me(ev).fadeOut() )
on(any('button'), 'click', ev => { me(ev).styles('color: red') })
- Run functions over elements.
any('button').run(_ => { alert(_) })
- Styles / CSS
me().styles('color: red')
me().styles({ 'color':'red', 'background':'blue' })
- Attributes
me().attribute('active', true)
<div>I change color every second.
<script>
// Every second animate something new.
me().on("click", async ev => {
me(ev).styles({ "transition": "background 1s" })
await sleep(1000)
me(ev).styles({ "background": "red" })
await sleep(1000)
me(ev).styles({ "background": "green" })
await sleep(1000)
me(ev).styles({ "background": "blue" })
await sleep(1000)
me(ev).styles({ "background": "none" })
await sleep(1000)
me(ev).remove()
})
</script>
</div>
<div>I fade out and remove myself.
<script>me().on("click", ev => { me(ev).fadeOut() })</script>
</div>
<div>I change color every second.
<script>
// Run immediately.
(async (e = me()) => {
me(e).styles({ "transition": "background 1s" })
await sleep(1000)
me(e).styles({ "background": "red" })
await sleep(1000)
me(e).styles({ "background": "green" })
await sleep(1000)
me(e).styles({ "background": "blue" })
await sleep(1000)
me(e).styles({ "background": "none" })
await sleep(1000)
me(e).remove()
})()
</script>
</div>
<script>
// Run immediately, for every <button> globally!
(async () => {
any("button").fadeOut()
})()
</script>
any('button')?.forEach(...)
any('button')?.map(...)
Looking for DOM Selectors? Looking for stuff we recommend doing in vanilla JS?
- ๐ Chainable off
me()
andany()
- ๐ Global convenience helper.
โถ๏ธ Runnable example.- ๐ Built-in Plugin
- ๐
run
- It's
forEach
but less wordy and works on single elements, too! โถ๏ธ me().run(e => { alert(e) })
โถ๏ธ any('button').run(e => { alert(e) })
- It's
- ๐
remove
โถ๏ธ me().remove()
โถ๏ธ any('button').remove()
- ๐
classAdd
๐class_add
๐addClass
๐add_class
โถ๏ธ me().classAdd('active')
- Leading
.
is optional for all class functions, and is removed automatically.- These are the same:
me().classAdd('active')
๐me().classAdd('.active')
- These are the same:
- ๐
classRemove
๐class_remove
๐removeClass
๐remove_class
โถ๏ธ me().classRemove('active')
- ๐
classToggle
๐class_toggle
๐toggleClass
๐toggle_class
โถ๏ธ me().classToggle('active')
- ๐
styles
โถ๏ธ me().styles('color: red')
Add style.โถ๏ธ me().styles({ 'color':'red', 'background':'blue' })
Add multiple styles.โถ๏ธ me().styles({ 'background':null })
Remove style.
- ๐
attribute
๐attributes
๐attr
- Get:
โถ๏ธ me().attribute('data-x')
- Get is only for single elements. For many, wrap the call in
any(...).run(...)
orany(...).forEach(...)
.
- Get is only for single elements. For many, wrap the call in
- Set:
โถ๏ธ me().attribute('data-x', true)
- Set multiple:
โถ๏ธ me().attribute({ 'data-x':'yes', 'data-y':'no' })
- Remove:
โถ๏ธ me().attribute('data-x', null)
- Remove multiple:
โถ๏ธ me().attribute({ 'data-x': null, 'data-y':null })
- Get:
- ๐
trigger
โถ๏ธ me().trigger('hello')
- Wraps
dispatchEvent
- ๐
on
โถ๏ธ me().on('click', ev => { me(ev).styles('background', 'red') })
- Wraps
addEventListener
- ๐
off
โถ๏ธ me().remove('click')
- Wraps
removeEventListener
- ๐
offAll
โถ๏ธ me().offAll()
- ๐
sleep
โถ๏ธ await sleep(1000, ev => { alert(ev) })
async
version ofsetTimeout
- Wonderful for animation timelines.
- ๐
tick
โถ๏ธ await tick()
await
version ofrAF
/requestAnimationFrame
.- Animation tick. Waits 1 frame.
- Great if you need to wait for events to propagate.
- ๐
rAF
โถ๏ธ rAF(e => { return e })
- Animation tick. Fires when 1 frame has passed. Alias of requestAnimationFrame
- Great if you need to wait for events to propagate.
- ๐
rIC
โถ๏ธ rIC(e => { return e })
- Great time to compute. Fires function when JS is idle. Alias of requestIdleCallback
- ๐
halt
โถ๏ธ halt(event)
- Great to prevent default browser behavior: such as displaying an image vs letting JS handle it.
- Wrapper for preventDefault
- ๐
createElement
๐create_element
โถ๏ธ e_new = createElement("div"); me().prepend(e_new)
- Alias of vanilla
document.createElement
- ๐
onloadAdd
๐onload_add
๐addOnload
๐add_onload
โถ๏ธ onloadAdd(_ => { alert("loaded!"); })
- Execute after the DOM is ready. Similar to jquery
ready()
- Queues functions onto
window.onload
- Why? So you don't overwrite
window.onload
, also predictable sequential loading!
Build effects with me().styles({...})
with timelines using CSS transitioned await
or callbacks.
Common effects included:
-
๐
fadeOut
๐fade_out
- Fade out and remove element.
- Keep element with
remove=false
. โถ๏ธ me().fadeOut()
โถ๏ธ me().fadeOut(ev => { alert("Faded out!") }, 3000)
Over 3 seconds then call function.
-
๐
fadeIn
๐fade_in
- Fade in existing element which has
opacity: 0
โถ๏ธ me().fadeIn()
โถ๏ธ me().fadeIn(ev => { alert("Faded in!") }, 3000)
Over 3 seconds then call function.
- Fade in existing element which has
More often than not, Vanilla JS is the easiest way!
Logging
- ๐
console.log()
console.warn()
console.error()
- Event logging:
โถ๏ธ monitorEvents(me())
See: Chrome Blog
Benchmarking / Time It!
โถ๏ธ console.time('name')
โถ๏ธ console.timeEnd('name')
Text / HTML Content
โถ๏ธ me().textContent = "hello world"
- XSS Safe! See: MDN
โถ๏ธ me().innerHTML = "<p>hello world</p>"
โถ๏ธ me().innerText = "hello world"
Children
โถ๏ธ me().children
โถ๏ธ me().children.hidden = true
Append / Prepend elements.
โถ๏ธ me().prepend(new_element)
โถ๏ธ me().appendChild(new_element)
โถ๏ธ me().insertBefore(element, other_element.firstChild)
โถ๏ธ me().insertAdjacentHTML("beforebegin", new_element)
_
= for temporary or unused variables. Keep it short and sweet!e
,el
,elt
= elemente
,ev
,evt
= eventf
,fn
= function- Many things can be done in vanilla HTML / CSS (ex: dropdowns).
- Find where your change touches the least code.
- Simplicity and ergonomics tend to have exponential payoff.
Feel free to modify Surreal for a particular project any way you like- but you can use this system to effortlessly merge functions with new versions.
- Add your function
var $thing = {
test(e, name) {
console.log(`Hello ${name} from ${e}`)
return e
}
}
$ = {...$, ...$thing}
- Is your function chainable? Add to
sugar()
$.sugars['test'] = (name) => { return $.test($._e, name) }
- Your function will be added globally by
globalsAdd()
If you do not want this (ex: name clash), add it to the restricted list.
Refer to an existing function to see how to make your function work with 1 or many elements.
Make an issue or pull request if you think people would like to use it! If it's useful enough we'll want it in core.
โญ Awesome Surreal examples, plugins, and resources: awesome-surreal !
- jQuery for the chainable syntax we all love.
- BlingBling.js for modern minimalism.
- Bliss.js for a focus on single elements and extensibility.
- Hyperscript for Locality of Behavior and awesome ergonomics.
- Shout out to Umbrella, Cash, Zepto- Not quite as ergonomic. Requires build step to extend.
- Always more
example.html
goodies! - Automated browser testing perhaps with: