Surreal
🗿 Mini jQuery alternative. Dependency-free animations. Locality of Behavior. Use one element or arrays transparently. Pairs with htmx. Vanilla querySelector() but better!
Install / Use
/learn @gnat/SurrealREADME
🗿 Surreal
Tiny jQuery alternative for plain Javascript with inline Locality of Behavior!
(Art by shahabalizadeh)
Why does this exist?
For devs who love ergonomics! You may appreciate Surreal if:
- You want to stay as close as possible to Vanilla JS.
- Hate typing
document.querySelectorover.. and over.. - Hate typing
addEventListenerover.. and over.. - Really wish
document.querySelectorAllhad Array functions.. - Really wish
thiswould 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. ✈️
✨ What does it add to Javascript?
- ⚡️ Locality of Behavior (LoB) Use
me()inside<script>- No .class or #id needed! Get an element without creating a unique name.
thisbut much more flexible!- Want
mein 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:
classAddorclass_addoraddClassoradd_class- Use
camelCase(Javascript) orsnake_case(Python, Rust, PHP, Ruby, SQL, CSS).
- Use
🤔 Why use me() / any() instead of $()
- 💡 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.
👁️ How does it look?
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.
🎁 Install
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>
⚡ Usage
<a name="selectors"></a>🔍️ DOM Selection
- Select one element:
me(...)- Can be any of:
- CSS selector:
".button","#header","h1","body > .block" - Variables:
body,e,some_element - Events:
event.currentTargetwill 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
.redto 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
🔥 DOM Functions
- ♻️ 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
<a name="quick-start"></a>⚡ Quick Start
- 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)
<a name="timelines"></a>
Timeline animations without any libraries.
<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>
Array methods
any('button')?.forEach(...)
any('button')?.map(...)
<a name="reference"></a>👁️ Functions
Looking for DOM Selectors? Looking for stuff we recommend doing in vanilla JS?
🧭 Legend
- 🔗 Chainable off
me()andany() - 🌐 Global shortcut.
- 🔥 Runnable example.
- 🔌 Built-in Plugin
👁️ At a glance
- 🔗
run- It's
forEachbut 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 c
- 🔥
