Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
dom-scheduler
Advanced tools
The DOM is fast, layout is fast, CSS transitions are smooth; but doing any at the same time can cause nasty performance glitches. This explains why it's easy to demo a 60fps transition, but larger apps are often janky.
dom-scheduler
helps express the types of operation you're doing, and in which order. Under the hood dom-scheduler
ensures everything happens with the best perceived performance.
$ npm install dom-scheduler
<script src="node_modules/dom-scheduler/dom-scheduler.js"></script>
This project has 2 main goals:
scheduler.attachDirect()
- Responding to long interactionsscheduler.feedback()
- Showing feedback to quick interactionscheduler.transition()
- Animations/transitionsscheduler.mutation()
- Mutating the DOMAs a rule of thumb
.feedback()
and .transition()
blocks should mainly contain hardware accelerated CSS transitions/animationsUsing debug mode with a browser timeline profiler can help you spot issues (eg. a feedback block causing a reflow). You can always refer to the excellent csstriggers.com while writing new code.
Let's take a simple example like adding an item at the top of a list. To do that smoothly we want to:
.transition()
everything down to make room for the new item.mutation()
to insert the new item into the DOM (outside of the viewport, so the item doesn't flash on screen).transition()
the new item in the viewportWithout dom-scheduler
this means:
setupTransitionOnElements();
container.addEventListener('transitionend', function trWait() {
container.removeEventListener('transitionend');
writeToTheDOM();
setupTransitionOnNewElement();
el.addEventListener('transitionend', function stillWaiting() {
el.removeEventListener('transitionend', stillWaiting);
cleanUp();
});
});
But we'd rather use promises to express this kind of sequence:
pushDown(elements)
.then(insertInDocument)
.then(slideIn)
.then(cleanUp)
Another badass sequence, using a promise-based storage system might be something like
Promise.all([reflectChangeWithTransitions(), persistChange()])
.then(reflectChangeInDocument)
.then(cleanUp)
reflectChangeWithTransition()
is a scheduled transitionpersitChange()
is your backend callreflectChangeInDocument
is a scheduled mutationcleanUp
is a scheduled mutationTo reap all the benefits from the scheduled approach you want to
scheduler
)Direct blocks should be used for direct manipulation (touchevents, scrollevents...). As such they have the highest priority.
You "attach" a direct block to a specific event. The scheduler takes care of adding and removing event listeners. The event object will be passed to the block
as the first parameter.
scheduler.attachDirect(elm, evt, block)
scheduler.detachDirect(elm, evt, block)
scheduler.attachDirect(el, 'touchmove', evt => {
el.style.transform = computeTransform(evt);
});
scheduler.feedback(block, elm, evt, timeout)
Feedback blocks should be used to encapsulate CSS transitions/animations triggered in direct response to a user interaction (eg. button pressed state).
They will be protected from scheduler.mutation()
s to perform smoothly and
return a promise, fulfilled once evt
is received on elm
or after timeout
ms.
The scheduler.feedback()
has the same priority as scheduler.attachDirect()
.
scheduler.feedback(() => {
el.classList.add('pressed');
}, el, 'transitionend').then(() => {
el.classList.remove('pressed');
});
scheduler.transition(block, elm, evt, timeout);
scheduler.transition()
should be used to protect CSS transitions/animations. When in progress they prevent any scheduled scheduler.mutation()
tasks running to maintain a smooth framerate. They return a promise, fulfilled once evt
is received on elm
or after timeout
ms.
scheduler.transition(() => {
el.style.transition = 'transform 0.25s ease';
el.classList.remove('new');
}, el, 'transitionend').then(() => {
el.style.transition = '';
});
scheduler.mutation(block);
Mutations blocks should be used to write to the DOM or perform actions requiring layout to be computed.
We shoud always aim for the document to be (almost) visually identical before and after a mutation block. Any big change in layout/size will cause a flash/jump.
scheduler.mutation()
blocks might be delayed (eg. when a scheduler.transition()
is in progress). They return a promise, fullfilled once the task is eventually executed; this also allows chaining.
When used for measurement (eg. getBoundingClientRect()
) a block can return
a result that will be propagated through the promise chain.
scheduler.mutation(() => {
el.textContent = 'Main List (' + items.length + ')';
});
.direct()
blocks are called inside requestAnimationFrame
.attachDirect()
and .feedback()
blocks have the highest priority and delay the rest..transition()
delays executuon of .mutation()
tasks..transition()
s are postponed while delayed mutation()
s are being flushed.transition()
s and .mutation()
s are queued because of .attachDirect()
manipulation, .transition()
s are run firstWhile it can have a negative impact on performance, it's recommended to turn the debug mode on from time to time during development to catch frequent mistakes early on.
Currently the debug mode will warn you about
We're also using console.time
/ console.timeEnd
to flag the
following in the profiler:
animating
, when a feedback or transition is ongoingprotecting
, when a direct protection window is ongoingYou can turn on the debug mode by setting debug
to true
in dom-scheduler.js
.
To illustrate the benefits of the scheduling approach the project comes with a demo app: a big re-orderable (virtual) list where new content comes in every few seconds. At random, the data source will sometime simulate a case where the content isn't ready. And delay populating the content.
The interesting part is of course the "real life" behaviors:
(You can turn on the naive
flag in dom-scheduler.js
to disable scheduling and compare.)
The index.html
at the root of this repository is meant for broad device and browser testing so we try to keep gecko/webkit/blink compatibility.
A (potentially outdated) version of the demo is usually accessible at http://sgz.fr/ds and should work on any modern browser.
The examples/demo
is a 'certified' packaged-app where we experiment with web components and other stuff.
$ npm install
$ npm test
If you would like tests to run on file change use:
$ npm run test-dev
Run lint check with command:
$ npm run lint
Mozilla Public License 2.0
FAQs
Making the performant thing the easy thing.
We found that dom-scheduler demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.