update-classes
A simple JavaScript utility for updating DOM-element's classlist
A small wrapper built around Element.classList
with support of multiple DOM-elements, conditional class application, several ways to specify updated classes, calls chaining and more
Changes in v1.1.0
Table of contents
Installation
npm:
npm i update-classes
yarn:
yarn add update-classes
Description
This small utility is designed to provide convenient tooling for modifications of DOM-element's classList and help to get rid of large amounts of repetitive code when updating classLists of elements, such as:
element1classListelement2classListelement1classListelement2classList
with this utility code above can be made much less repetitive and much more readable:
When should I use it?
When you are working on a small to medium scale project developed mostly with Vanilla JavaScript, or when you just playing in sandbox and don't want deal with complex UI frameworks and libraries. updateClasses also works great when combined with Stimulus
Can I use it with React?
While you certainly can make it work with React using React's ref API, you probably should avoid such practice. React was designed for rendering data to the DOM, which means it has all this functionality by default. If you want more convenient conditional rendering of css-classes in React, classnames utility is exactly what you need
Usage
Import updateClasses utility:
Applying classes to single or multiple targets
You can use it with single or multiple targets, just be sure to wrap them in an array there're more than one:
updateClasses(target1, { 'class-to-enable': true })updateClasses([ target1, target2 ], { 'class-to-enable': true })
Specifying which classes to update
There're multiple ways to point out which classes to be updated and how. CSS-classes that should be updated are always specified in second argument.
Object notation
With object notation classes are specified in object, where keys are CSS-classes that should be updated and values represent how these classes will be updated, values can be either of boolean or string type:
true
- add
false
- remove
string
- replace with specified class
This notation is very convenient when you want to replace multiple classes or apply them conditionally:
const currentScore = thisstate
String notation
In string notation updated classes are specified (surprise!) with a string, in which they should be separated with spaces and classes that are to be removed should have a !
prefix:
// with no prefix class will be added // with prefix '!' it will be removed // with prefix '~' it will be toggled // You're also free to specify as many classes as you want in a single string:
This notation is convenient when you don't really need to replace or conditionally update classes and want to make your code as readable as possible.
Array notation
Array allows to specify several class updates similar to string notation (!
is also relevant):
but it also allows to specify its elements with any other notation:
you can even specify classes in array inside another array ...and inside another array ...and inside another array
Even though I can't see why you would commit such atrocity, but if you feel like it keep in mind that you can!
Chaining calls
If you need to update multiple different classes on multiple elements and avoid repeating typing duplicate code, you can use call chaining. After every call updateClasses
returns an object where methods .also()
and .and()
refer to itself so you can use this shorthand to avoid repeating yourself:
Chainable options
It is now possible to create additional updateClasses instances, with specific presets. It is useful when you need to call it with same arguments multiple times or when you follow some rules involving class scoping (like BEM).
There're 3 methods for creating new instances of updateClasses
function: scope
, target
, classes
.
scope
Method scope
creates new instance of updateClasses in which every passed class will be prefixed with provided string:
const updateFoo = updateClassesscope'foo__' someElement1classList // -> [ 'foo__button', 'foo__icon', 'foo__label' ] // If you decide to add another scope it will be added AFTER the current scope const updateFooBar = updateFooscope'bar--' someElement2classList // -> [ 'foo__bar--active', 'foo__bar--red', 'foo__bar--left' ]
target
Method target
creates new instance of updateClasses which will be binded to provided specific target:
const updateMyElement = updateClassestargetmyElement myElementclassList // -> [ 'foo' ] // You still can pass other targets if you want, but binded target will always be modified myElementclassList // -> [ 'foo', 'bar' ]otherElementclassList // -> [ 'bar' ]
classes
Method classes
creates new instance of updateClasses which will always add/remove/toggle provided classes from target:
const makeFoo = updateClassesclasses'foo' someElementclassList // -> [ 'foo' ]
Classes can be provided in any notation.
Note
Every instance created by scope
, target
, classes
methods is immutable and completely independent from its parent
Under the hood
Every instance of updateClasses now contains options
object which is can't be accessed from outside and has following signature:
If you really need to see options
object, you can recieve its copy with __extractOptions
method:
// Default options objectupdateClasses // -> { scope: '', ensureTargets: [], ensureClasses: [] }
Each updateClasses instance has separate options object.
Working with BEM notation
With chainable scope
method you can now easily work using BEM (Block Element Modifier) methodology:
HTML:
JS:
const myBlock = documentconst myBlockButton = document const updateMyBlock = updateClassesscope'my-block' const updateMyBlockModifier = updateMyBlockscope'--'myBlockclassList // -> [ 'my-block--active' ] const makeMyBlockElement = updateMyBlockscope'__'myBlockButtonclassList // -> [ 'my-block__button' ] const updateMyBlockButtonModifier = makeMyBlockButtonscope'--'myBlockButtonclassList // -> [ 'my-block__button', 'my-block__button--disabled' ]
Handling animations and transitions
CSS is also used a lot to animate things, and at lot of times after animation (or transition) you need to remove class that triggered animation (perhaps you want to animate this element again in the future) or add another class (to start a different animation sequence for example). For these cases the return object contains .afterAnimation()
and .afterTransition()
methods, which will not apply classes immediatly, but after animation or transition sequence on updating elements will end. They're also don't have target parameter, all targets are being inherited from an original call:
// here we apply class with display none after animation finishes to avoid interruptions
Note: Don't use these methods for excessevly long animations as some browsers kill animation and transition sequences when user switches to another tab which will make animationend
and transitionend
events to never appear and listeners applied by these methods will continiously and pointlessly wait for them producing memeory leaks. Also don't interrupt animations with properties that cause repaint of elements (such as display: none
) as this doesn't produce corresponding events and leads to same problem.
Basically treat these methods as a shorthand for manually applying animationend
and transitionend
event listeners.
Browser support
Full support of class updates
- Edge 12+
- Firefox 3.6+
- Chrome 8+
- Safari 5.1+
- Opera 11.5+
- iOS Safari/Chrome 5+
- Opera Mini
Partial support of class updates
- Internet Explorer 10+ (does not support class replacement)
Might work on older browsers with classList polyfill.
.afterAnimation()
and .afterTransition()
methods
Support of Depends on browser's support of animationend
and transitionend
events.
License
MIT