Tiny jQuery alternative for plain Javascript with 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 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>
- No .class or #id needed! Get an element without creating a unique name.
this
but much more flexible!- 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
- ๐ก Solves the classic jQuery 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 = write less code. Bonus: 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).classRemove('.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 320 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>
- 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()
- Choose the start location in the DOM with the 2nd arg. (Default:
document
)- ๐ฅ
any('button', me('#header')).classAdd('red')
- Add
.red
to any<button>
inside of#header
- Add
- ๐ฅ
- CSS selector:
me()
โญ Get parent element of<script>
without a .class or #id !me("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(...)
- Like
me()
but guaranteed to return an array (or empty array). any(".foo")
โญ Get all matching elements.- Convert between arrays of elements and single elements:
any(me())
,me(any(".something"))
- Like
- โป๏ธ 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 you of any clobbering issues!- ๐๐ฉธ If you want no conveniences, or are a masochist, delete
globalsAdd()
- ๐ข
me().classAdd('red')
becomessurreal.me().classAdd('red')
- ๐
classAdd(me(), 'red')
becomessurreal.classAdd(surreal.me(), 'red')
- ๐ข
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() )
any('button').on('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>
// 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?
- ๐ 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- Same thing:
me().classAdd('active')
๐me().classAdd('.active')
- Same thing:
- ๐ฅ
- ๐
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')
- For single elements.
- For many elements, wrap it in:
any(...).run(...)
orany(...).forEach(...)
- 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: ๐ฅ
- ๐
send
๐trigger
- ๐ฅ
me().send('change')
- ๐ฅ
me().send('change', {'data':'thing'})
- Wraps
dispatchEvent
- ๐ฅ
- ๐
on
- ๐ฅ
me().on('click', ev => { me(ev).styles('background', 'red') })
- Wraps
addEventListener
- ๐ฅ
- ๐
off
- ๐ฅ
me().off('click', fn)
- 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)
- Prevent default browser behaviors.
- 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!"); })
- ๐ฅ
<script>let e = me(); onloadAdd(_ => { me(e).on("click", ev => { alert("clicked") }) })</script>
- Execute after the DOM is ready. Similar to jquery
ready()
- Add to
window.onload
while preventing overwrites ofwindow.onload
and predictable loading! - Alternatives:
- Skip missing elements using
?.
example:me("video")?.requestFullscreen()
- Place
<script>
after the loaded element.- See
me('-')
/me('prev')
- See
- Skip missing elements using
- ๐ฅ
- ๐
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 (replace jQuery ajax()
)
- Use htmx or htmz or fetch() or XMLHttpRequest() directly.
- Using
fetch()
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 done in HTML / CSS (ex: dropdowns)
_
= for temporary or unused variables. Keep it short and sweet!e
,el
,elt
= elemente
,ev
,evt
= eventf
,fn
= function
- โญ On
me()
me().hey = (text) => { alert(text) }
me().on('click', (ev) => { me(ev).hey("hi") })
- โญ Use a block:
{ function hey(text) { alert(text) }; me().on('click', ev => { hey("hi") }) }
- โญ Use an event:
me().on('click', ev => { /* add and call function here */ })
- Use an inline module:
<script type="module">
- Note:
me()
will no longer seeparentElement
so explicit selectors are required:me(".mybutton")
- Note:
- Use:
me('-')
orme('prev')
orme('previous')
- ๐ฅ
<input type="text" /> <script>me('-').value = "hello"</script>
- Inspired by the CSS "next sibling" combinator
+
but in reverse-
- ๐ฅ
- Or, use a relative start.
- ๐ฅ
<form> <input type="text" n1 /> <script>me('[n1]', me()).value = "hello"</script> </form>
- ๐ฅ
- ๐ฅ
me("#i_dont_exist")?.classAdd('active')
- No warnings: ๐ฅ
me("#i_dont_exist", document, false)?.classAdd('active')
Feel free to edit Surreal directly- but if you prefer, you can use plugins to effortlessly merge 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)
Now use your function like: me().hello("Internet")
- See the included
pluginEffects
for a more comprehensive example. - Your functions are added globally by
globalsAdd()
If you do not want this, add it to therestricted
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: