Tiny jQuery alternative for plain Javascript featuring inline Locality of Behavior!
(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 340 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.currentTarget
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, or are a masochist, delete
globalsAdd()
me().classAdd('red')
becomes:surreal.me().classAdd('red')
classAdd(me(), 'red')
becomes:surreal.classAdd(surreal.me(), 'red')
- If you prefer no conveniences, or are a masochist, 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 => {
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>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 shortcut.
▶️ 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()
- 🔗
disable
▶️ me().disable()
- Easy alternative to
off()
. Disables click, key, submit events.
- 🔗
enable
▶️ me().enable()
- Opposite of
disable()
- 🌐
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!
- 🔌
fadeOut
- See below
- 🔌
fadeIn
- See below
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)
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}`) }
})
- Using XMLHttpRequest()
▶️
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()
})
- Many ideas can be plain HTML / CSS (ex: dropdowns).
_
= for temporary or unused variables. Keep it short and sweet!e
,el
,elt
= elemente
,ev
,evt
= eventf
,fn
= function- Scoping functions inside
<script>
(..or anything not scoped byme()
)- ⭐ Inside a
me()
event:me().on('click', ev => { /* add and call function here */ })
- Or, use an inline module:
<script type="module">
- Note:
me()
will no longer seeparentElement
so explicit selectors are required:me(".mybutton")
- Note:
- Or, use backend code to generate unique names for anything not scoped by
me()
- ⭐ Inside a
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")
- See the included
pluginEffects
for a more comprehensive example. - Your functions will be added globally by
globalsAdd()
If you do not want this, add it to the restricted list. - Refer to an existing function to see how to make yours 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: