<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yangren</title>
    <description>The latest articles on DEV Community by Yangren (@aayla_secura).</description>
    <link>https://dev.to/aayla_secura</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1704478%2F12bce427-1f7e-4285-ae96-c1e104d7aa87.png</url>
      <title>DEV Community: Yangren</title>
      <link>https://dev.to/aayla_secura</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aayla_secura"/>
    <language>en</language>
    <item>
      <title>🌊Layout thrashing: what is it and how to eliminate it</title>
      <dc:creator>Yangren</dc:creator>
      <pubDate>Sat, 01 Mar 2025 11:47:35 +0000</pubDate>
      <link>https://dev.to/aayla_secura/layout-thrashing-what-is-it-and-how-to-eliminate-it-n2j</link>
      <guid>https://dev.to/aayla_secura/layout-thrashing-what-is-it-and-how-to-eliminate-it-n2j</guid>
      <description>&lt;p&gt;Have you ever come across (or event built!) a cool, modern, sleek, animation-heavy website... and opened it on mobile only to see that it runs like Alan Wake 2 on a 2003 Dell Inspiron?&lt;/p&gt;

&lt;h2&gt;
  
  
  What is layout thrashing?
&lt;/h2&gt;

&lt;p&gt;Layout thrashing, a.k.a. forced synchronous reflow, occurs when the browser is continuously &lt;strong&gt;forced to drop its optimized queueing of layout and style recalculations and do it ASAP&lt;/strong&gt; because a script running needs to know the latest layout measurements.&lt;/p&gt;

&lt;p&gt;Because layout re-calculation and repainting are resource-heavy operations, if the browser is forced to do them too often and not "at the right time", this leads to very jittery laggy and possibly unresponsive page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What causes it?
&lt;/h2&gt;

&lt;p&gt;As a user interacts with the page and layout changes or animations play, the browser has to keep re-rendering the latest version of a page. It does in roughly two distinct steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;🌊 &lt;strong&gt;Reflow&lt;/strong&gt;: re-calculate the position, size, etc of the elements on the page&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;Repaint&lt;/strong&gt;: re-draw elements whose appearance has changed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(I refer you to &lt;a href="https://dev.to/gopal1996/understanding-reflow-and-repaint-in-the-browser-1jbg"&gt;this great article&lt;/a&gt; for more info on those.)&lt;/p&gt;

&lt;p&gt;How often reflow and repaint happen depends on the screen refresh rate and on the what the JavaScript is doing. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ideally this happens at most once every display refresh "tick"&lt;/strong&gt;, i.e. 60, 75 or more times a second (the screen refresh rate). This is because, as mentioned, reflow and repaint are quite resource expensive and lead to laggy performance when done more often than that.&lt;/p&gt;

&lt;p&gt;However, unless the scripts on the page have been specifically optimized for that, &lt;strong&gt;chances are good that reflows and/or repaints are happening multiple times in each animation frame&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Normally browsers are pretty good at optimizing the reflows/repaints and defer them until just before the next display refresh tick. The browser is also clever about caching layout information and knows whether it actually needs to re-calculate it or not by keeping track of what has changed since the last reflow, i.e. whether the style/layout has been "invalidated". But if a script reads or "measures" a layout property, such as the height of an element or its offset on the page, &lt;em&gt;after&lt;/em&gt; the style has been invalidated, then the browser is forced to re-calculate now in order to give the correct measurement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjbvezsndxevbe4tc0yh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjbvezsndxevbe4tc0yh.jpg" alt="Image description" width="640" height="497"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://yonatankra.com/layout-reflow/" rel="noopener noreferrer"&gt;&lt;em&gt;Image credit&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;So for a forced synchronous reflow to happen, then &lt;strong&gt;2 things need to follow in order:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A script modifies the style in a way that &lt;strong&gt;invalidates the current style&lt;/strong&gt;, by for example adding a CSS class or directly modifying the inline style. From now on I will refer to this as a "&lt;strong&gt;mutation&lt;/strong&gt;".&lt;/li&gt;
&lt;li&gt;Then it &lt;strong&gt;asks for measurement&lt;/strong&gt; that depends on the latest style such as the height of an element. From now on I will refer to this as a "&lt;strong&gt;measurement&lt;/strong&gt;".&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2shpohrogithya2ihd6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2shpohrogithya2ihd6.png" alt="Image description" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to avoid it?
&lt;/h2&gt;

&lt;p&gt;The ideal order of operations that avoids forced reflows is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start of frame: Browser runs is scheduled (optimized) &lt;strong&gt;recalculation&lt;/strong&gt; and repaint.&lt;/li&gt;
&lt;li&gt;JavaScript can freely read or "&lt;strong&gt;measure&lt;/strong&gt;" any layout values such as &lt;code&gt;offsetHeight&lt;/code&gt;, &lt;code&gt;clientHeight&lt;/code&gt;, &lt;code&gt;getBoundingClientRect&lt;/code&gt;, &lt;code&gt;getComputedStyle&lt;/code&gt;, &lt;code&gt;event.offsetX&lt;/code&gt;, etc. See &lt;a href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a" rel="noopener noreferrer"&gt;here&lt;/a&gt; for a comprehensive list of "measurement" operations that if not done on a valid layout will force synchronous reflow.&lt;/li&gt;
&lt;li&gt;... more calculations&lt;/li&gt;
&lt;li&gt;End of frame: &lt;code&gt;requestAnimationFrame&lt;/code&gt; callbacks fire; callbacks can now "&lt;strong&gt;mutate&lt;/strong&gt;" the style by adding/removing CSS classes, adding, removing, moving elements, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;To achieve this&lt;/strong&gt;, the general principle is to&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Batch and defer all style mutations and to &lt;em&gt;just before&lt;/em&gt; the end of the frame, which is the scheduled time for reflows.&lt;/li&gt;
&lt;li&gt;Ensure that any measurements are done &lt;em&gt;after&lt;/em&gt; a scheduled reflow has happened and &lt;em&gt;before&lt;/em&gt; the style has been invalidated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1 is easy: any code that needs to modify the style should run in a callback to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame" rel="noopener noreferrer"&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/a&gt;. You can do this yourself or use something like &lt;a href="https://github.com/wilsonpage/fastdom" rel="noopener noreferrer"&gt;fastdom&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 1: batch all mutations to the end of the frame&lt;/span&gt;

&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... modify the DOM, e.g.&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cls&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newChild&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2 could be a bit tricky. If &lt;em&gt;all&lt;/em&gt; the code running on the webpage is yours and &lt;em&gt;all&lt;/em&gt; your style modifications are batched and deferred as mentioned in step 1, then you can safely just do all measurements at any time &lt;em&gt;except&lt;/em&gt; inside the mutation callbacks (because at that time the style has been invalidated and doing measurements will force a reflow).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 2: Simpler naive approach&lt;/span&gt;
&lt;span class="c1"&gt;// Just do your measurements synchronously,&lt;/span&gt;
&lt;span class="c1"&gt;// assuming that all mutations will be deferred&lt;/span&gt;
&lt;span class="c1"&gt;// till the end of the frame&lt;/span&gt;

&lt;span class="c1"&gt;// Fingers crossed the style is not invalidated here&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in most cases &lt;strong&gt;not all of the code on the page is yours and other code isn't so considerate&lt;/strong&gt; so as to defer its mutations to the end of the frame, and so when you do a measurement and it happens to run in an already invalidated style it will force a reflow. &lt;a href="https://github.com/wilsonpage/fastdom" rel="noopener noreferrer"&gt;fastdom&lt;/a&gt; takes a simple approach by also deferring all measurements, in addition to mutations, to the end of the frame, and runs all measurements first, then all mutations. This ensures that a reflow forced by your code can happen at most once in a frame: during the first measurement task that runs in the &lt;code&gt;requestAnimationFrame&lt;/code&gt; callback. Then at the end of the frame of course there will be a scheduled reflow, because the style would have been invalidated by the mutation tasks.&lt;/p&gt;

&lt;p&gt;For this reason I chose to not use fastdom in &lt;a href="https://lisnjs.github.io/" rel="noopener noreferrer"&gt;my latest project&lt;/a&gt; but wrote &lt;a href="https://github.com/lisnjs/lisn.js/blob/main/packages/lisn.js/src/ts/utils/dom-optimize.ts" rel="noopener noreferrer"&gt;my own implementation&lt;/a&gt; in which I:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Batch and &lt;strong&gt;defer mutations till the end of the frame&lt;/strong&gt; as before.&lt;/li&gt;
&lt;li&gt;Batch and &lt;strong&gt;defer measurements till just after the start of the next frame&lt;/strong&gt; when the style is almost certainly still valid, having just been re-calculated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now forced reflows by my own code are eliminated (even if other code on the page has mutated the layout during the frame).&lt;/p&gt;

&lt;p&gt;Step 2 is achieved by first waiting for the &lt;code&gt;requestAnimationFrame&lt;/code&gt; callbacks to fire, which happens &lt;em&gt;just before&lt;/em&gt; a paint, and then scheduling a high priority task using either the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Scheduler/postTask" rel="noopener noreferrer"&gt;Scheduler.postTask method&lt;/a&gt; which is only available in recent browsers, or the fallback legacy &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel" rel="noopener noreferrer"&gt;MessageChannel&lt;/a&gt;. These two methods schedule a new task (&lt;em&gt;not&lt;/em&gt; a microtask, but a &lt;del&gt;macro&lt;/del&gt;task) that runs with higher priority than say &lt;code&gt;setTimeout&lt;/code&gt;. I refer you to &lt;a href="https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif"&gt;this excellent post&lt;/a&gt; on the event loop and tasks.&lt;/p&gt;

&lt;p&gt;This works because tasks scheduled from inside &lt;code&gt;requestAnimationFrame&lt;/code&gt; callbacks will be deferred till after any scheduled reflow/repaint that will follow said callbacks. Again note that I'm not talking about &lt;em&gt;microtasks&lt;/em&gt; such as Promise resolve callbacks: these will run in the same frame, before the repaint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 2: A more robust approach&lt;/span&gt;
&lt;span class="c1"&gt;// Defer your measurements till after the next scheduled reflow&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scheduleHighPriorityTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user-blocking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback to MessageChannel&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MessageChannel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// end of frame here, mutations will run/have just run&lt;/span&gt;
  &lt;span class="nf"&gt;scheduleHighPriorityTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// start of next frame: scheduled reflow has just happened&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to test for it?
&lt;/h2&gt;

&lt;p&gt;The browser's built-in Performance monitoring tool can help you spot forced reflows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Performance tab and start recording&lt;/li&gt;
&lt;li&gt;Do something on the page that you suspect causes reflows&lt;/li&gt;
&lt;li&gt;Stop recording and examine the timeline. Force reflows will be little purple boxes that say "Recalculate style" that appear below the top row of events (ones on the top row will be the scheduled ones that run at the optimal time). When you click on those boxes it will also tell you which line of which file caused that:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6vyokizzvmb8iypns9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6vyokizzvmb8iypns9d.png" alt="Image description" width="800" height="843"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://codepen.io/AaylaSecura/pen/wBvgmaE?editors=1010" rel="noopener noreferrer"&gt;the simple pen&lt;/a&gt; I wrote that you can use to play with it (screenshot taken from there when running the unoptimized version).&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR;
&lt;/h2&gt;

&lt;p&gt;Layout thrashing is when the browser is forced to perform a synchronous, out-of-schedule, re-calculation of the style because JavaScript has previously modified the layout or style and invalidated the browser's cached measurements, and now JavaScript wants to read the latest value of a property from the layout.&lt;/p&gt;

&lt;p&gt;To avoid it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Batch and defer all code that modified the DOM till &lt;em&gt;just before the end of the frame&lt;/em&gt; using &lt;code&gt;requestAnimationFrame&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Batch and defer all your measurements till &lt;em&gt;just after the start of the next frame&lt;/em&gt; using &lt;code&gt;requestAnimationFrame&lt;/code&gt; and then a high priority task scheduler like &lt;code&gt;Scheduler&lt;/code&gt; or &lt;code&gt;MessageChannel&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;Thank you for reading, like if you like, &lt;a href="https://x.com/AaylaSecura1138" rel="noopener noreferrer"&gt;follow me you X&lt;/a&gt;. And be happy and kind 🌞&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources/references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing" rel="noopener noreferrer"&gt;Avoid large, complex layouts and layout thrashing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webperf.tips/tip/layout-thrashing/" rel="noopener noreferrer"&gt;Layout Thrashing and Forced Reflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yonatankra.com/layout-reflow/" rel="noopener noreferrer"&gt;What is a Forced Reflow and How to Solve it?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/gopal1996/understanding-reflow-and-repaint-in-the-browser-1jbg"&gt;Understanding Reflow and Repaint in the browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif"&gt;JavaScript Visualized: Event Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a" rel="noopener noreferrer"&gt;What forces layout/reflow. The comprehensive list.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>🦄Cool things with CSS only: carousel, slider, menu, tabs, input animation</title>
      <dc:creator>Yangren</dc:creator>
      <pubDate>Mon, 24 Feb 2025 12:44:22 +0000</pubDate>
      <link>https://dev.to/aayla_secura/cool-things-with-css-only-carousel-slider-menu-tabs-input-animation-4e0g</link>
      <guid>https://dev.to/aayla_secura/cool-things-with-css-only-carousel-slider-menu-tabs-input-animation-4e0g</guid>
      <description>&lt;p&gt;CSS is becoming more and more powerful. Static CSS animations of course have been around for a long time, but nowadays, on modern browsers at least, you can even implement certain scroll-based animations with pure CSS.&lt;/p&gt;

&lt;p&gt;But CSS has also been used for a long time for implementing other interactive features that normally require JavaScript. This is not a new technique; there are already tons of examples of implementing tabbed content, sandwich menus and so on using the "checkbox trick".&lt;/p&gt;

&lt;p&gt;Here I'll show some variations on those two I mentioned, plus a few more interesting examples of CSS-only interactivity.&lt;/p&gt;

&lt;p&gt;I've also made the examples as compatible as possible with old browsers. 🦖🦖 &lt;strong&gt;No scroll snapping, not even using &lt;code&gt;:has&lt;/code&gt; selector!&lt;/strong&gt; Instead, relying on good old sibling selectors. &lt;/p&gt;

&lt;h2&gt;
  
  
  Image slider
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;⚡ Add as many images as you want, same CSS&lt;/li&gt;
&lt;li&gt;🦖 Works on all browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/AaylaSecura/embed/azbZVKR?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;This slider uses images that are overlayed on top of each other with the "upcoming" ones being translated to the right, out of view. In between each image there's a checkbox. Checkboxes that are ticked are displayed on the left hand side, and unchecked ones on the right. Of course they are invisible and their respective labels are styled as arrows and overlayed on top.&lt;/p&gt;

&lt;p&gt;All images associated with a ticked checkbox are in-view, and all the other ones are out of view. The trick here is to use a flat structure with each checkbox and image following one another, so that you can find images corresponding to unchecked ones using the sibling selector &lt;code&gt;~&lt;/code&gt; alone, and so that you don't have to hard code the number of images and rely on &lt;code&gt;:nth-child&lt;/code&gt; pseudo-selector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Carousel
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;😎 Auto-slides overflowed ones into view&lt;/li&gt;
&lt;li&gt;🦖 Works on all browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/AaylaSecura/embed/yyLJQbM?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;This carousel slider has items that can be focused. If the slides don't fit on the page, their wrapping element will be automatically translated so that the current slide is in view.&lt;/p&gt;

&lt;p&gt;It relies on &lt;code&gt;tabindex&lt;/code&gt; and the &lt;code&gt;:focus&lt;/code&gt; pseudo-selector to make each slide (in this case a simple image) focusable (using click or TAB). The CSS uses media queries and some trickery to calculate the width and transforms for the slider container. The downside is that you need to partly hardcode the number of slides in by providing selectors for each possible number of slides. You can do super easily with SCSS and loop until some very large number (it doesn't need to equal the number of slides, just be &amp;gt;= to that number). But here I've kept it simple and manually laid those out for up to 10 slides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page tabs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The good old one, here for completeness&lt;/li&gt;
&lt;li&gt;🦖 Works on all browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/AaylaSecura/embed/wBvWQQz?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;Similar to the slider, but using radio buttons instead of checkboxes&lt;/p&gt;

&lt;h2&gt;
  
  
  Valid input animation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🙃 Just for fun! Or an idea for &lt;a href="https://lisnjs.github.io/demos/sortable/" rel="noopener noreferrer"&gt;something more interesting...&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🦖 Works on all browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/AaylaSecura/embed/dPyXzeX?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;There are several inputs each one having a &lt;code&gt;pattern&lt;/code&gt; and &lt;code&gt;required&lt;/code&gt; attributes in order to enable the use of the &lt;code&gt;:valid&lt;/code&gt; pseudo-selector.&lt;/p&gt;

&lt;p&gt;It lays the inputs out in a grid. The grid has a "ghost" column and a "ghost" row using some dummy line elements to create spacing around the inputs. Then relying only on sibling selectors (&lt;code&gt;+&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt;) we add a state where all 8 inputs are valid and then shrink the ghost lines to bring all inputs together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sandwich menu (auto-closing)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🦄 Auto-closes on clicking outside&lt;/li&gt;
&lt;li&gt;🦖 Works on all browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/AaylaSecura/embed/PwozXRK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;The well known checkbox trick here as well: have a checkbox and a label overlayed on top and styles as the menu button. The menu content is a direct sibling of it (again, in order to avoid using &lt;code&gt;:has&lt;/code&gt;). When the check is ticked, the menu is brought into view, otherwise translated out of view. The addition here compared to the standard examples is a &lt;strong&gt;second&lt;/strong&gt; label for the same checkbox that acts as the page overlay so that clicking either the close button (the first label) or the second label, closes the menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;Thank you for reading, like if you like. And be happy 🌞&lt;/p&gt;

</description>
      <category>purecss</category>
      <category>webdev</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Introducing LISN.js: handle user interactions and layout events + widgets! ⚡</title>
      <dc:creator>Yangren</dc:creator>
      <pubDate>Mon, 17 Feb 2025 14:06:45 +0000</pubDate>
      <link>https://dev.to/aayla_secura/introducing-lisnjs-handle-user-interactions-and-layout-events-widgets-4119</link>
      <guid>https://dev.to/aayla_secura/introducing-lisnjs-handle-user-interactions-and-layout-events-widgets-4119</guid>
      <description>&lt;p&gt;Hey there awesome people! Firstly, let me briefly say hello, this will be my first post on dev.to 👋 I want to introduce you to my new project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is LISN.js?
&lt;/h2&gt;

&lt;p&gt;In a nutshell, LISN.js is a flexible, full-featured and simple to use library for handling user gestures (drag, scroll, zoom, with any type of device) and interactions (like scrolling, clicking, etc) as well as observing elements for changes in viewport position, size and so on.&lt;/p&gt;

&lt;p&gt;There are React wrappers available as a separate package. It works in&lt;br&gt;
server-side rendering environments like Next.js.&lt;/p&gt;

&lt;p&gt;LISN also comes with many awesome widgets, like collapsible, floating popup, modal, offcanvas menu, pager (carousel/slider/tabs), flex same-height, scrollbars (native scrolling), sortable, scroll-to-top button and page loader.&lt;/p&gt;


  &lt;a href="https://lisnjs.github.io/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Flisnjs%2Flisn.js%2Fmaster%2Fwebsite%2FLISN%2520logo.png" alt="LISN.js" width="800" height="800"&gt;&lt;/a&gt;



&lt;h2&gt;
  
  
  "Why not use X, Y and Z?" you say
&lt;/h2&gt;

&lt;p&gt;So you go like, "Well there's Hammer.js for gesture handling, Waypoints for viewport observation, Trigger.js or GSAP for scroll based animations, Sortable.js for sortables, OverlayScrollbars for scrollbars, and gosh-you-name-it for modals, popups, sliders, and so on? What's new here 🤷&lt;/p&gt;

&lt;p&gt;Well you &lt;em&gt;can&lt;/em&gt; use all of those (and I'd probably recommend each one ❤️). But in general, do you prefer to install 15+ libraries, learn how to use each one, possibly survive a dependency hell, find out that some of them may not be actively maintained, some of them don't work in server-side rendering context, some of them are too rigid and don't allow for extensive customization, some of them don't work with touch or on browser X... (P.S. I'm not saying this is the case for any of the libraries I just mentioned)... or do you prefer to install 1 library that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% vanilla TypeScript (no dependencies)&lt;/li&gt;
&lt;li&gt;Has React wrappers&lt;/li&gt;
&lt;li&gt;Extensively tested&lt;/li&gt;
&lt;li&gt;Highly optimized, no layout thrashing&lt;/li&gt;
&lt;li&gt;Works with server-side rendering&lt;/li&gt;
&lt;li&gt;Super flexible and customizable&lt;/li&gt;
&lt;li&gt;Simple to use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and in addition to the standard JavaScript API it has an &lt;strong&gt;HTML-only mode&lt;/strong&gt; where you can do almost anything the full JS API can do without writing a single line of JavaScript.&lt;/p&gt;

&lt;p&gt;Want a modal to open the first time the user scrolls to the middle of the page? Easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;data-lisn-modal&lt;/span&gt;
  &lt;span class="na"&gt;data-lisn-on-view=&lt;/span&gt;&lt;span class="s"&gt;"@open +target=top: 50% +rootMargin=-48%,0px +once"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    Here's a modal popping up to tell you you've reached the middle of the page!
  &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"footnote"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;P.S. Don't do that, popups are annoying.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want scroll-tabs/sections that show the current section in the page and can scroll to it (smoothly and as slowly as you like)? And a cool configurable scrollbar (maybe as a progress bar)? Here, just sprinkle some CSS to taste.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tabs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-ref=&lt;/span&gt;&lt;span class="s"&gt;"tab1"&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-on-view=&lt;/span&gt;&lt;span class="s"&gt;"at @add-class:inview
                         +root=next.scrollable
                         +rootMargin=0px
                         +target=next-section1"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Intro
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-ref=&lt;/span&gt;&lt;span class="s"&gt;"tab2"&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-on-view=&lt;/span&gt;&lt;span class="s"&gt;"at @add-class:inview
                         +root=next.scrollable
                         +rootMargin=0px
                         +target=next-section2"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      The real deal
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"scrollable"&lt;/span&gt;
       &lt;span class="na"&gt;data-lisn-scrollbar=&lt;/span&gt;&lt;span class="s"&gt;"positionV=top | click-scroll=false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-ref=&lt;/span&gt;&lt;span class="s"&gt;"section1"&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-on-click=&lt;/span&gt;&lt;span class="s"&gt;"@scroll-to: offsetY=-5, scrollable=this.scrollable
                          +target=prev-tab1 +oneWay"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- target is the click target --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Yada yada...&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      Does anyone even read intro?
      Norem hipsum color something something
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-ref=&lt;/span&gt;&lt;span class="s"&gt;"section2"&lt;/span&gt;
      &lt;span class="na"&gt;data-lisn-on-click=&lt;/span&gt;&lt;span class="s"&gt;"@scroll-to: offsetY=-5, scrollable=this.scrollable
                          +target=prev-tab2 +oneWay"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Now we're talking&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      ...
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🙄 But I'm an experienced dev and I write &lt;del&gt;JavaScript&lt;/del&gt; TypeScript (React TypeScript!)
&lt;/h2&gt;

&lt;p&gt;Sure, you can still do that with LISN and the syntax/API is just as simple and intuitive and even more powerful. (👉 &lt;a href="https://lisnjs.github.io/demos/" rel="noopener noreferrer"&gt;Demos&lt;/a&gt; | &lt;a href="https://lisnjs.github.io/docs/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ZKTMAQjXYw0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;But for the curious, I'll explain how the HTML-only mode came about; well actually how and why LISN itself came to be!&lt;/p&gt;

&lt;p&gt;The HTML-only mode was actually the original aspect of LISN before it underwent 3-4 stages of metamorphosis 🦋 You see, I was making a website... in Wordpress... (don't ask). And let me tell you, it ain't easy to make it look like and walk like and talk like the animal you want 🦖 A lot of the Wordpress plugins are limited, rigid and full of bugs.&lt;/p&gt;

&lt;p&gt;Sigh... I just wanted to have nice, glitch-free customizable modals and collapsibles... And animate elements when they come into view... And scroll to an alert box when the page loads... And have a flexbox section with an image and text that stay the same height no matter what aspect ratio image I put in there (cause it's all dynamic™ you know). (If you've ever tried to do the last one and ended up writing &lt;a href="https://github.com/lisnjs/lisn.js/blob/main/packages/lisn.js/src/ts/widgets/same-height.ts#L17" rel="noopener noreferrer"&gt;3 pages of polynomial equations and calculus&lt;/a&gt; you'll know my &lt;del&gt;pain&lt;/del&gt; sense of reward.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegc2kxnl7ppmhettbtjd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegc2kxnl7ppmhettbtjd.jpg" alt="How hard can it be" width="353" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, I guess if you want a job done well, you gotta do it yourself. So I did. And then the idea came...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfvei739k1pxigp3w0zo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfvei739k1pxigp3w0zo.jpg" alt="LISN metamorphosis" width="532" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok, ok, docs, demos, now
&lt;/h2&gt;

&lt;p&gt;I won't copy paste the quick start and doc here, but just refer you to the latest docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://lisnjs.github.io/" rel="noopener noreferrer"&gt;Official website&lt;/a&gt; | &lt;a href="https://lisnjs.github.io/demos/" rel="noopener noreferrer"&gt;Demos&lt;/a&gt; | &lt;a href="https://lisnjs.github.io/docs/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt; | &lt;a href="https://github.com/lisnjs/lisn.js" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/h2&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>react</category>
    </item>
  </channel>
</rss>
