🗿 Mini jQuery alternative. Dependency-free animations. Locality of Behavior. Use one element or arrays transparently. Pairs with htmx. Vanilla querySelector() but better!
(Art by shahabalizadeh)
For devs who love ergonomics! You may appreciate Surreal if:
document.querySelector
over.. and over..addEventListener
over.. and over..document.querySelectorAll
had Array functions..this
would work in any inline <script>
tagme()
inside <script>
this
but better!me
in your CSS <style>
tags, too? See our companion script
me()
, any()
, NodeList
, HTMLElement
(..or arrays of these!)me()
any()
me()
or any()
can chain with any Surreal function.
me()
can be used directly as a single element (like querySelector()
or $()
)any()
can use: for
/ forEach
/ filter
/ map
(like querySelectorAll()
or $()
)classAdd
or class_add
or addClass
or add_class
camelCase
(Javascript) or snake_case
(Python, Rust, PHP, Ruby, SQL, CSS).me()
/ any()
instead of $()
me()
is guaranteed to return 1 element (or first found, or null).any()
is guaranteed to return an array (or empty array).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').send('change') })
</script>
</label>
See the Live Example! Then view source.
Surreal is only 340 lines. No build step. No dependencies.
📥 Download into your project, and add <script src="/surreal.js"></script>
in your <head>
Or, 🌐 via CDN: <script src="https://cdn.jsdelivr.net/gh/gnat/surreal@main/surreal.js"></script>
me(...)
".button"
, "#header"
, "h1"
, "body > .block"
body
, e
, some_element
event.currentTarget
will be used.me()
,any()
document
any('button', me('#header')).classAdd('red')
.red
to any <button>
inside of #header
me()
Get current element for Locality of Behavior in <script>
without an explicit .class or #id
me("body")
Gets <body>
me(".button")
Gets the first <div class="button">...</div>
. To get all of them use any()
any(...)
me()
but guaranteed to return an array (or empty array).any(".foo")
Gets all matching elements, such as: <div class="foo">...</div>
any(me())
, me(any(".something"))
me()
and any()
me().classAdd('red')
⭐ Chain style, recommended!classAdd(me(), 'red')
globalsAdd()
will automatically warn about any clobbering issues.
globalsAdd()
me().classAdd('red')
becomes: surreal.me().classAdd('red')
classAdd(me(), 'red')
becomes: surreal.classAdd(surreal.me(), 'red')
See: Quick Start and Reference and No Surreal Needed
me().classAdd('red')
any("button").classAdd('red')
me().on("click", ev => me(ev).fadeOut() )
on(any('button'), 'click', ev => { me(ev).styles('color: red') })
any('button').run(_ => { alert(_) })
me().styles('color: red')
me().styles({ 'color':'red', 'background':'blue' })
me().attribute('active', true)
<div>I change color every second.
<script>
// On click, animate something new every second.
me().on("click", async ev => {
let el = me(ev) // Save target because async will lose it.
me(el).styles({ "transition": "background 1s" })
await sleep(1000)
me(el).styles({ "background": "red" })
await sleep(1000)
me(el).styles({ "background": "green" })
await sleep(1000)
me(el).styles({ "background": "blue" })
await sleep(1000)
me(el).styles({ "background": "none" })
await sleep(1000)
me(el).remove()
})
</script>
</div>
<div>I fade out and remove myself.
<script>me().on("click", ev => { me(ev).fadeOut() })</script>
</div>
<div>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?
me()
and any()
run
forEach
but less wordy and works on single elements, too!me().run(e => { alert(e) })
any('button').run(e => { alert(e) })
remove
me().remove()
any('button').remove()
classAdd
🔁 class_add
🔁 addClass
🔁 add_class
me().classAdd('active')
.
is optional for all class functions, and is removed automatically.
me().classAdd('active')
🔁 me().classAdd('.active')
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
me().attribute('data-x')
any(...).run(...)
or any(...).forEach(...)
.me().attribute('data-x', true)
me().attribute({ 'data-x':'yes', 'data-y':'no' })
me().attribute('data-x', null)
me().attribute({ 'data-x': null, 'data-y':null })
send
🔁 trigger
me().send('change')
me().send('change', {'data':'thing'})
dispatchEvent
on
me().on('click', ev => { me(ev).styles('background', 'red') })
addEventListener
off
me().remove('click')
removeEventListener
offAll
me().offAll()
disable
me().disable()
off()
. Disables click, key, submit events.enable
me().enable()
disable()
sleep
await sleep(1000, ev => { alert(ev) })
async
version of setTimeout
tick
await tick()
await
version of rAF
/ requestAnimationFrame
.rAF
rAF(e => { return e })
rIC
rIC(e => { return e })
halt
halt(event)
createElement
🔁 create_element
e_new = createElement("div"); me().prepend(e_new)
document.createElement
onloadAdd
🔁 onload_add
🔁 addOnload
🔁 add_onload
onloadAdd(_ => { alert("loaded!"); })
ready()
window.onload
window.onload
, also predictable sequential loading!fadeOut
fadeIn
Build effects with me().styles({...})
with timelines using CSS transitioned await
or callbacks.
Common effects included:
🔗 fadeOut
🔁 fade_out
remove=false
.me().fadeOut()
me().fadeOut(ev => { alert("Faded out!") }, 3000)
Over 3 seconds then call function.🔗 fadeIn
🔁 fade_in
opacity: 0
me().fadeIn()
me().fadeIn(ev => { alert("Faded in!") }, 3000)
Over 3 seconds then call function.More often than not, Vanilla JS is the easiest way!
Logging
console.log()
console.warn()
console.error()
monitorEvents(me())
See: Chrome Blog
Benchmarking / Time It!
console.time('name')
console.timeEnd('name')
Text / HTML Content
me().textContent = "hello world"
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)
Ajax (alternatives to jquery ajax()
)
me().on("click", async event => {
let e = me(event)
// Example 1: Hit an endpoint.
if((await fetch("/webhook")).ok) console.log("Did the thing.")
// Example 2: Get content and replace me()
try {
let response = await fetch('/endpoint')
if (response.ok) e.innerHTML = await response.text()
else console.warn('fetch(): Bad response')
}
catch (error) { console.warn(`fetch(): ${error}`) }
})
me().on("click", async event => {
let e = me(event)
// Example 1: Hit an endpoint.
var xhr = new XMLHttpRequest()
xhr.open("GET", "/webhook")
xhr.send()
// Example 2: Get content and replace me()
var xhr = new XMLHttpRequest()
xhr.open("GET", "/endpoint")
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) e.innerHTML = xhr.responseText
}
xhr.send()
})
_
= for temporary or unused variables. Keep it short and sweet!e
, el
, elt
= elemente
, ev
, evt
= eventf
, fn
= function<script>
me()
me().doIt = (message) => { alert(message) }
me().on('click', (ev) => { me(ev).doIt("hello") })
me().on('click', ev => { /* add and call function here */ })
<script type="module">
me()
will no longer see parentElement
so explicit selectors are required: me(".mybutton")
me()
<input type="text" />
me('-')
or me('prev')
or me('previous')
<input type="text" /> <script>me('-').value = "hello"</script>
me(document.currentScript.previousElementSibling)
+
but in reverse -
<input type="text" n1 /> <script>me('[n1]', me()).value = "hello"</script>
me("#i_dont_exist")?.classAdd('active')
me("#i_dont_exist", document, false)?.classAdd('active')
Feel free to modify Surreal for a project any way you like- but you can use plugins to effortlessly merge functions with new versions.
function pluginHello(e) {
function hello(e, name="World") {
console.log(`Hello ${name} from ${e}`)
return e // Make chainable.
}
// Add sugar
e.hello = (name) => { return hello(e, name) }
}
surreal.plugins.push(pluginHello)
You can now use it like: me().hello("Internet")
pluginEffects
for a more comprehensive example.globalsAdd()
If you do not want this, add it to the restricted list.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.
example.html
goodies!