wake-marquee 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -1
- package/README.md +19 -2
- package/dist/wake-marquee.auto.js +1 -1
- package/dist/wake-marquee.js +1 -1
- package/package.json +1 -1
- package/src/marquee.js +61 -13
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,36 @@ While the version is below `1.0.0` the API may still change in a minor release.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.2.2] - 2026-09-02
|
|
12
|
+
|
|
13
|
+
A row whose container carries a transform was being measured on the axis the
|
|
14
|
+
screen sees rather than the one it travels along.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- A container carrying a `transform` is measured on the axis the row actually
|
|
19
|
+
travels along. Every geometry read went through `getBoundingClientRect()`,
|
|
20
|
+
which reports the box after the transform, so a row under
|
|
21
|
+
`rotate(±90deg)` handed the loop its own thickness as its width and ran on
|
|
22
|
+
numbers wrong by its aspect ratio: three lanes where seven were needed,
|
|
23
|
+
leaving the last two thirds of the edge empty, a period that wrapped short,
|
|
24
|
+
and a wake and a percentage speed at a fraction of what was asked for. None
|
|
25
|
+
of it errored. The loop's geometry is now read in the row's own coordinate
|
|
26
|
+
system, which is where the lanes are translated and where the browser
|
|
27
|
+
resolves the overhang, so a rotated row behaves exactly like a flat one.
|
|
28
|
+
Any angle works, not only right angles.
|
|
29
|
+
- The wake amplitude is read back off the overhang the browser resolved,
|
|
30
|
+
rather than being worked out from the same percentage a second time. The two
|
|
31
|
+
could disagree, which under a `scale()` cost half the wake and on a
|
|
32
|
+
container with inline padding a little more than the reserve.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- A percentage `speed` under a `scale()` is now a fraction of the container
|
|
37
|
+
as the reader sees it, which is what `'8%'` reads as. It was measured
|
|
38
|
+
against the already scaled box and then rendered scaled again, so a row
|
|
39
|
+
inside `scale(0.5)` ran at a quarter of its speed rather than a half.
|
|
40
|
+
|
|
11
41
|
## [0.2.1] - 2026-09-01
|
|
12
42
|
|
|
13
43
|
Two ways the row could still rearrange itself in front of the reader
|
|
@@ -134,7 +164,8 @@ First release.
|
|
|
134
164
|
- Right-to-left writing modes are not supported. Flex rows follow the writing
|
|
135
165
|
direction while the transforms are physical, so the two disagree.
|
|
136
166
|
|
|
137
|
-
[Unreleased]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.
|
|
167
|
+
[Unreleased]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.2...HEAD
|
|
168
|
+
[0.2.2]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.1...v0.2.2
|
|
138
169
|
[0.2.1]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.0...v0.2.1
|
|
139
170
|
[0.2.0]: https://github.com/robin-gogolok/wake-marquee/compare/v0.1.0...v0.2.0
|
|
140
171
|
[0.1.0]: https://github.com/robin-gogolok/wake-marquee/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -361,6 +361,23 @@ The same rule, the other way round. Declare the layer order before the imports,
|
|
|
361
361
|
|
|
362
362
|
A layer registered late wins, and an `@import` after `tailwindcss` registers `wake-marquee` after `utilities`. Declared up front, it sits where it belongs: your utilities still win.
|
|
363
363
|
|
|
364
|
+
## A row on its side
|
|
365
|
+
|
|
366
|
+
There is no vertical mode. Rotate the container:
|
|
367
|
+
|
|
368
|
+
```css
|
|
369
|
+
.edge {
|
|
370
|
+
width: 30rem; /* the edge it runs along, not its thickness */
|
|
371
|
+
height: 2.75rem;
|
|
372
|
+
transform-origin: 0 0;
|
|
373
|
+
transform: rotate(90deg);
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The loop is measured in the row's own coordinate system rather than off the screen, so a rotated row gets the period, the lane count, the wake and a percentage speed it would have had lying flat. Any angle works, and so does a `scale()` on the row or on anything above it.
|
|
378
|
+
|
|
379
|
+
The one thing to write down is `width`: for a row on its side that is the edge it travels along, and nothing in CSS infers it from the height of whatever it is framing. The demo frames a block with four rows, two of them turned.
|
|
380
|
+
|
|
364
381
|
## Accessibility
|
|
365
382
|
|
|
366
383
|
- **The content is announced once.** Clones are `inert` and `aria-hidden`, so a screen reader hears eight logos, not the forty that fill the track. Where `inert` is missing, focusable elements inside clones get `tabindex="-1"` instead, so they never become invisible tab stops.
|
|
@@ -392,9 +409,9 @@ There is no build-time or CSS-only version of this, and there cannot be one whil
|
|
|
392
409
|
|
|
393
410
|
Filling the track means copying the row until it covers the container, typically two to four times. That is cheap for images and text and expensive for anything with its own runtime: iframes, videos, canvases and framework components with state get copied along with everything else. Rows of that kind want a different approach.
|
|
394
411
|
|
|
395
|
-
###
|
|
412
|
+
### Left to right
|
|
396
413
|
|
|
397
|
-
|
|
414
|
+
A right-to-left writing mode is not supported: the flex rows follow the writing direction while the transforms are physical, so the two disagree and the loop runs the wrong way. An `rtl` page can still use this inside an `ltr` container. `writing-mode` is the same story. For a vertical row, rotate the container instead, which is supported.
|
|
398
415
|
|
|
399
416
|
### One measurement, one period
|
|
400
417
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var WakeMarquee=(()=>{var p=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var b=i=>{throw TypeError(i)};var J=(i,e)=>{for(var t in e)p(i,t,{get:e[t],enumerable:!0})},W=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of K(e))!Y.call(i,o)&&o!==t&&p(i,o,{get:()=>e[o],enumerable:!(s=V(e,o))||s.enumerable});return i};var X=i=>W(p({},"__esModule",{value:!0}),i);var Z=(i,e,t)=>e.has(i)||b("Cannot "+t);var E=(i,e,t)=>e.has(i)?b("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(i):e.set(i,t);var n=(i,e,t)=>(Z(i,e,"access private method"),t);var fe={};J(fe,{initMarquees:()=>f});function ee(i,e,t){return i<e?e:i>t?t:i}function x(i,e,t=1){return!(e>0)||!Number.isFinite(i)?1:Math.ceil(i/e)+1+t}function M(i,e){if(typeof i=="number")return i;let t=parseFloat(i);return Number.isFinite(t)&&e>0?t*e/100:0}function C(i,e,t,s){return s>0?((i+e*t)%s+s)%s:0}function S(i,e,t,s){return i+(e-i)*(1-Math.exp(-t*s))}function A(i,e,t){let s=t+e;return s>0?ee((t-i)/s,0,1):0}function q(i,e,t){return-t*e*(1-2*i)}function F(i,e=.1){return!Number.isFinite(i)||i<=0?0:Math.min(i/1e3,e)}var te=Object.freeze({direction:"left",speed:60,wake:8,reverse:!0,ease:5,gap:null,fade:!1,pauseOnHover:!1,scroller:null,respectMotionPreference:!0}),T="data-wake-marquee",ie="wake-lane",I="wake-track",se=1,re="200px 0px",ne=3e3,oe=/^\d*\.?\d+%$/;function ae(i){return typeof i=="string"?oe.test(i):Number.isFinite(i)&&i>=0}function he(i){return i.firstElementChild?.classList.contains(I)===!0}var u=new Set,P=new WeakMap,m=new Map,w=0,c=0,L=!1,R=!1;function N(i){return i===window?window.scrollY:i.scrollTop}function de(i){let e=m.get(i);if(!e){m.set(i,{last:N(i),sign:1});return}let t=N(i);Math.abs(t-e.last)>.5&&(e.sign=t>e.last?1:-1,e.last=t)}function z(i){return m.get(i)?.sign??1}function O(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function ue(i={}){let e={...te};for(let[t,s]of Object.entries(i))s!==void 0&&(e[t]=s);if(e.direction!=="left"&&e.direction!=="right")throw new RangeError(`wake-marquee: direction must be "left" or "right", received "${e.direction}"`);if(typeof e.speed=="string"&&(e.speed=e.speed.trim()),!ae(e.speed)){let t=typeof e.speed=="string"?`"${e.speed}"`:e.speed;throw new RangeError(`wake-marquee: speed must be a non-negative number of pixels per second, or a percentage of the container width per second such as "8%", received ${t}`)}if(!Number.isFinite(e.wake)||e.wake<0)throw new RangeError(`wake-marquee: wake must be a non-negative number, received ${e.wake}`);if(!Number.isFinite(e.ease)||e.ease<=0)throw new RangeError(`wake-marquee: ease must be a positive number, received ${e.ease}`);return e.scroller=e.scroller??(typeof window>"u"?null:window),e}function ce(i){let e=i.dataset,t={};if(e.wakeDirection&&(t.direction=e.wakeDirection),e.wakeSpeed){let s=e.wakeSpeed.trim();t.speed=Number.isFinite(Number(s))?Number(s):s}return e.wake&&(t.wake=Number(e.wake)),e.wakeEase&&(t.ease=Number(e.wakeEase)),e.wakeGap&&(t.gap=e.wakeGap),e.wakeFade&&(t.fade=e.wakeFade),e.wakePauseOnHover!==void 0&&(t.pauseOnHover=!0),e.wakeReverse!==void 0&&(t.reverse=e.wakeReverse!=="false"),t}var r,$,D,B,v,l,H,U,k,_,G,Q,y,g=class{constructor(e,t){E(this,r);this.element=e,this.options=ue(t),this.dirSign=this.options.direction==="right"?1:-1,this.dirFactor=this.dirSign,this.offset=0,this.period=0,this.speedPx=0,this.wakePx=0,this.amplitude=0,this.aligned=!1,this.waiting=null,this.visible=!1,this.measured=!1,this.paused=!1,this.hovered=!1,this.destroyed=!1,this.status="",n(this,r,$).call(this),n(this,r,U).call(this),u.add(this),P.set(e,this),n(this,r,k).call(this)}prime(){if(this.destroyed||this.measured)return this;let e=window.innerHeight,t=this.element.getBoundingClientRect(),s=200;return t.bottom<-s||t.top>e+s?this:(this.visible=!0,this.element.toggleAttribute("data-wake-active",!0),n(this,r,v).call(this),this)}refresh(){if(this.destroyed)return;let e=this.lane.getBoundingClientRect().width;if(!(e>0))return;this.period>0&&e!==this.period&&(this.offset=this.offset/this.period*e),this.measured||n(this,r,G).call(this),this.period=e,this.measured=!0,n(this,r,D).call(this);let t=x(this.track.getBoundingClientRect().width,e,se);for(;this.lanes.length<t;){let s=this.lane.cloneNode(!0);s.inert=!0,s.setAttribute("aria-hidden","true"),"inert"in s||s.querySelectorAll("a, button, input, select, textarea, [tabindex]").forEach(o=>o.setAttribute("tabindex","-1")),s.querySelectorAll('img[loading="lazy"]').forEach(o=>o.setAttribute("loading","eager")),s.style.transform=this.lane.style.transform,this.track.appendChild(s),this.lanes.push(s)}for(;this.lanes.length>t;)this.lanes.pop()?.remove();d()}read(e){if(!n(this,r,y).call(this))return;let t=this.element.getBoundingClientRect(),s=A(t.top,t.height,e);this.amplitude=this.options.wake*t.width/100,this.wakePx=q(s,this.amplitude,this.dirSign),this.speedPx=M(this.options.speed,t.width)}write(e,t){if(!n(this,r,y).call(this))return;this.aligned||n(this,r,Q).call(this);let s=this.hovered?0:this.options.reverse?this.dirSign*t:this.dirSign;this.dirFactor=S(this.dirFactor,s,e,this.options.ease),this.offset=C(this.offset,this.dirFactor*this.speedPx,e,this.period);let a=`translate3d(${(this.offset-this.period).toFixed(2)}px, 0, 0)`;for(let j of this.lanes)j.style.transform=a;this.track.style.transform=`translate3d(${this.wakePx.toFixed(2)}px, 0, 0)`;let h=t===1?"forward":"reversed";h!==this.status&&(this.status=h,this.element.setAttribute("data-wake-travel",h))}play(){return this.destroyed?this:this.options.respectMotionPreference&&O()?this:(this.paused=!1,d(),this)}pause(){return this.paused=!0,this}destroy(){if(!this.destroyed){this.destroyed=!0,u.delete(this),P.delete(this.element),this.waiting?.(),this.intersection?.disconnect(),this.resize?.disconnect(),this.onEnter&&this.element.removeEventListener("pointerenter",this.onEnter),this.onLeave&&this.element.removeEventListener("pointerleave",this.onLeave),this.motionQuery&&this.onMotionChange&&this.motionQuery.removeEventListener("change",this.onMotionChange);for(let e of this.lanes.slice(1))e.remove();for(;this.lane.firstChild;)this.element.appendChild(this.lane.firstChild);this.track.remove(),this.element.removeAttribute("data-wake-travel"),this.element.removeAttribute("data-wake-active");for(let e of this.undo)e();this.undo=[],this.lanes=[]}}};r=new WeakSet,$=function(){let e=this.element,t=e.ownerDocument;for(this.track=t.createElement("div"),this.track.className=I,this.lane=t.createElement("div"),this.lane.className=ie;e.firstChild;)this.lane.appendChild(e.firstChild);this.track.appendChild(this.lane),e.appendChild(this.track),this.lanes=[this.lane],this.undo=[];let s=(a,h)=>{e.hasAttribute(a)||(e.setAttribute(a,h),this.undo.push(()=>e.removeAttribute(a)))},o=(a,h)=>{e.style.getPropertyValue(a)||(e.style.setProperty(a,h),this.undo.push(()=>e.style.removeProperty(a)))};s(T,""),this.options.gap&&o("--wake-gap",this.options.gap),this.options.fade&&(o("--wake-fade",this.options.fade),s("data-wake-fade",""))},D=function(){this.track.style.marginInlineStart=`-${this.options.wake}%`,this.track.style.width=`${100+this.options.wake*2}%`},B=function(){let e=[];for(let t of this.lane.querySelectorAll("img"))!t.complete&&t.getBoundingClientRect().width===0&&e.push(t);return e},v=function(){if(this.waiting)return;let e=n(this,r,B).call(this);if(e.length===0){n(this,r,l).call(this);return}n(this,r,H).call(this);let t=e.length,s=()=>{--t>0||(a(),n(this,r,l).call(this))},o=setTimeout(()=>{a(),n(this,r,l).call(this)},ne),a=()=>{this.waiting=null,clearTimeout(o);for(let h of e)h.removeEventListener("load",s),h.removeEventListener("error",s)};this.waiting=a;for(let h of e)h.addEventListener("load",s),h.addEventListener("error",s)},l=function(){this.destroyed||(this.refresh(),this.period>0&&!this.paused&&(this.read(window.innerHeight),this.write(0,z(this.options.scroller))))},H=function(){R||(R=!0,console.warn("wake-marquee: an item holds no width until it loads, so the loop period is not measurable yet and the row is waiting. Give each <img> its width and height attributes, or an aspect-ratio, and it starts immediately."))},U=function(){let e=this.element;this.intersection=new IntersectionObserver(t=>{for(let s of t)this.visible=s.isIntersecting,e.toggleAttribute("data-wake-active",s.isIntersecting),s.isIntersecting&&!this.measured&&n(this,r,v).call(this);d()},{rootMargin:re,threshold:0}),this.intersection.observe(e),this.resize=new ResizeObserver(()=>{this.measured&&this.refresh()}),this.resize.observe(this.lane),this.resize.observe(e),this.options.pauseOnHover&&matchMedia("(hover: hover)").matches&&(this.onEnter=()=>{this.hovered=!0},this.onLeave=()=>{this.hovered=!1,d()},e.addEventListener("pointerenter",this.onEnter),e.addEventListener("pointerleave",this.onLeave)),this.options.respectMotionPreference&&typeof matchMedia=="function"&&(this.motionQuery=matchMedia("(prefers-reduced-motion: reduce)"),this.onMotionChange=()=>n(this,r,k).call(this),this.motionQuery.addEventListener("change",this.onMotionChange))},k=function(){if(!this.options.respectMotionPreference){d();return}O()?(this.paused=!0,n(this,r,_).call(this)):(this.paused=!1,d())},_=function(){this.offset=0,this.wakePx=0,this.aligned=!1;for(let e of this.lanes)e.style.transform="";this.track.style.transform=""},G=function(){if(L)return;let e=this.lane.firstElementChild;if(!e)return;let t=getComputedStyle(e),s=t.getPropertyValue("--wake-gap").trim();!((s===""?32:parseFloat(s))>0)||parseFloat(t.marginInlineEnd)!==0||(L=!0,console.warn("wake-marquee: the items have no spacing. An unlayered CSS reset outranks @layer wake-marquee whatever the specificity says. Put the reset in a layer and name the order first: @layer reset, wake-marquee;"))},Q=function(){this.aligned=!0;let e=this.amplitude-this.wakePx;this.offset=(e%this.period+this.period)%this.period},y=function(){return!this.destroyed&&!this.paused&&this.visible&&this.period>0};function le(i){let e=c===0?0:F(i-c);c=i;let t=window.innerHeight;for(let s of u)s.options.reverse&&de(s.options.scroller),s.read(t);for(let s of u)s.write(e,s.options.reverse?z(s.options.scroller):1);w=0,d()}function d(){if(w!==0)return;let i=!1;for(let e of u)if(!e.destroyed&&!e.paused&&e.visible){i=!0;break}if(!i){c=0;return}w=requestAnimationFrame(le)}function f({root:i=document,defaults:e={}}={}){let t=[];for(let s of i.querySelectorAll(`[${T}]`))he(s)||t.push(new g(s,{...e,...ce(s)}));for(let s of t)s.prime();return t}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>f(),{once:!0}):f());return X(fe);})();
|
|
1
|
+
var WakeMarquee=(()=>{var p=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var b=i=>{throw TypeError(i)};var Y=(i,e)=>{for(var t in e)p(i,t,{get:e[t],enumerable:!0})},J=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let h of W(e))!K.call(i,h)&&h!==t&&p(i,h,{get:()=>e[h],enumerable:!(s=V(e,h))||s.enumerable});return i};var X=i=>J(p({},"__esModule",{value:!0}),i);var Z=(i,e,t)=>e.has(i)||b("Cannot "+t);var E=(i,e,t)=>e.has(i)?b("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(i):e.set(i,t);var o=(i,e,t)=>(Z(i,e,"access private method"),t);var pe={};Y(pe,{initMarquees:()=>f});function ee(i,e,t){return i<e?e:i>t?t:i}function x(i,e,t=1){return!(e>0)||!Number.isFinite(i)?1:Math.ceil(i/e)+1+t}function S(i,e){if(typeof i=="number")return i;let t=parseFloat(i);return Number.isFinite(t)&&e>0?t*e/100:0}function M(i,e,t,s){return s>0?((i+e*t)%s+s)%s:0}function C(i,e,t,s){return i+(e-i)*(1-Math.exp(-t*s))}function A(i,e,t){let s=t+e;return s>0?ee((t-i)/s,0,1):0}function F(i,e,t){return-t*e*(1-2*i)}function q(i,e=.1){return!Number.isFinite(i)||i<=0?0:Math.min(i/1e3,e)}var te=Object.freeze({direction:"left",speed:60,wake:8,reverse:!0,ease:5,gap:null,fade:!1,pauseOnHover:!1,scroller:null,respectMotionPreference:!0}),I="data-wake-marquee",ie="wake-lane",T="wake-track",se=1,re="200px 0px",ne=3e3,oe=/^\d*\.?\d+%$/;function ae(i){return typeof i=="string"?oe.test(i):Number.isFinite(i)&&i>=0}function he(i){return parseFloat(getComputedStyle(i).width)}function de(i){return i.firstElementChild?.classList.contains(T)===!0}var u=new Set,P=new WeakMap,m=new Map,w=0,l=0,L=!1,N=!1;function O(i){return i===window?window.scrollY:i.scrollTop}function ue(i){let e=m.get(i);if(!e){m.set(i,{last:O(i),sign:1});return}let t=O(i);Math.abs(t-e.last)>.5&&(e.sign=t>e.last?1:-1,e.last=t)}function z(i){return m.get(i)?.sign??1}function R(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function le(i={}){let e={...te};for(let[t,s]of Object.entries(i))s!==void 0&&(e[t]=s);if(e.direction!=="left"&&e.direction!=="right")throw new RangeError(`wake-marquee: direction must be "left" or "right", received "${e.direction}"`);if(typeof e.speed=="string"&&(e.speed=e.speed.trim()),!ae(e.speed)){let t=typeof e.speed=="string"?`"${e.speed}"`:e.speed;throw new RangeError(`wake-marquee: speed must be a non-negative number of pixels per second, or a percentage of the container width per second such as "8%", received ${t}`)}if(!Number.isFinite(e.wake)||e.wake<0)throw new RangeError(`wake-marquee: wake must be a non-negative number, received ${e.wake}`);if(!Number.isFinite(e.ease)||e.ease<=0)throw new RangeError(`wake-marquee: ease must be a positive number, received ${e.ease}`);return e.scroller=e.scroller??(typeof window>"u"?null:window),e}function ce(i){let e=i.dataset,t={};if(e.wakeDirection&&(t.direction=e.wakeDirection),e.wakeSpeed){let s=e.wakeSpeed.trim();t.speed=Number.isFinite(Number(s))?Number(s):s}return e.wake&&(t.wake=Number(e.wake)),e.wakeEase&&(t.ease=Number(e.wakeEase)),e.wakeGap&&(t.gap=e.wakeGap),e.wakeFade&&(t.fade=e.wakeFade),e.wakePauseOnHover!==void 0&&(t.pauseOnHover=!0),e.wakeReverse!==void 0&&(t.reverse=e.wakeReverse!=="false"),t}var r,$,D,H,v,c,U,B,k,_,G,Q,y,g=class{constructor(e,t){E(this,r);this.element=e,this.options=le(t),this.dirSign=this.options.direction==="right"?1:-1,this.dirFactor=this.dirSign,this.offset=0,this.period=0,this.speedPx=0,this.wakePx=0,this.amplitude=0,this.width=0,this.aligned=!1,this.waiting=null,this.visible=!1,this.measured=!1,this.paused=!1,this.hovered=!1,this.destroyed=!1,this.status="",o(this,r,$).call(this),o(this,r,B).call(this),u.add(this),P.set(e,this),o(this,r,k).call(this)}prime(){if(this.destroyed||this.measured)return this;let e=window.innerHeight,t=this.element.getBoundingClientRect(),s=200;return t.bottom<-s||t.top>e+s?this:(this.visible=!0,this.element.toggleAttribute("data-wake-active",!0),o(this,r,v).call(this),this)}refresh(){if(this.destroyed)return;let e=he(this.lane);if(!(e>0))return;this.period>0&&e!==this.period&&(this.offset=this.offset/this.period*e),this.measured||o(this,r,G).call(this),this.period=e,this.measured=!0,o(this,r,D).call(this);let t=getComputedStyle(this.track),s=parseFloat(t.width);this.amplitude=-parseFloat(t.marginInlineStart),this.width=s-this.amplitude*2;let h=x(s,e,se);for(;this.lanes.length<h;){let n=this.lane.cloneNode(!0);n.inert=!0,n.setAttribute("aria-hidden","true"),"inert"in n||n.querySelectorAll("a, button, input, select, textarea, [tabindex]").forEach(a=>a.setAttribute("tabindex","-1")),n.querySelectorAll('img[loading="lazy"]').forEach(a=>a.setAttribute("loading","eager")),n.style.transform=this.lane.style.transform,this.track.appendChild(n),this.lanes.push(n)}for(;this.lanes.length>h;)this.lanes.pop()?.remove();d()}read(e){if(!o(this,r,y).call(this))return;let t=this.element.getBoundingClientRect(),s=A(t.top,t.height,e);this.wakePx=F(s,this.amplitude,this.dirSign),this.speedPx=S(this.options.speed,this.width)}write(e,t){if(!o(this,r,y).call(this))return;this.aligned||o(this,r,Q).call(this);let s=this.hovered?0:this.options.reverse?this.dirSign*t:this.dirSign;this.dirFactor=C(this.dirFactor,s,e,this.options.ease),this.offset=M(this.offset,this.dirFactor*this.speedPx,e,this.period);let n=`translate3d(${(this.offset-this.period).toFixed(2)}px, 0, 0)`;for(let j of this.lanes)j.style.transform=n;this.track.style.transform=`translate3d(${this.wakePx.toFixed(2)}px, 0, 0)`;let a=t===1?"forward":"reversed";a!==this.status&&(this.status=a,this.element.setAttribute("data-wake-travel",a))}play(){return this.destroyed?this:this.options.respectMotionPreference&&R()?this:(this.paused=!1,d(),this)}pause(){return this.paused=!0,this}destroy(){if(!this.destroyed){this.destroyed=!0,u.delete(this),P.delete(this.element),this.waiting?.(),this.intersection?.disconnect(),this.resize?.disconnect(),this.onEnter&&this.element.removeEventListener("pointerenter",this.onEnter),this.onLeave&&this.element.removeEventListener("pointerleave",this.onLeave),this.motionQuery&&this.onMotionChange&&this.motionQuery.removeEventListener("change",this.onMotionChange);for(let e of this.lanes.slice(1))e.remove();for(;this.lane.firstChild;)this.element.appendChild(this.lane.firstChild);this.track.remove(),this.element.removeAttribute("data-wake-travel"),this.element.removeAttribute("data-wake-active");for(let e of this.undo)e();this.undo=[],this.lanes=[]}}};r=new WeakSet,$=function(){let e=this.element,t=e.ownerDocument;for(this.track=t.createElement("div"),this.track.className=T,this.lane=t.createElement("div"),this.lane.className=ie;e.firstChild;)this.lane.appendChild(e.firstChild);this.track.appendChild(this.lane),e.appendChild(this.track),this.lanes=[this.lane],this.undo=[];let s=(n,a)=>{e.hasAttribute(n)||(e.setAttribute(n,a),this.undo.push(()=>e.removeAttribute(n)))},h=(n,a)=>{e.style.getPropertyValue(n)||(e.style.setProperty(n,a),this.undo.push(()=>e.style.removeProperty(n)))};s(I,""),this.options.gap&&h("--wake-gap",this.options.gap),this.options.fade&&(h("--wake-fade",this.options.fade),s("data-wake-fade",""))},D=function(){this.track.style.marginInlineStart=`-${this.options.wake}%`,this.track.style.width=`${100+this.options.wake*2}%`},H=function(){let e=[];for(let t of this.lane.querySelectorAll("img"))!t.complete&&t.getBoundingClientRect().width===0&&e.push(t);return e},v=function(){if(this.waiting)return;let e=o(this,r,H).call(this);if(e.length===0){o(this,r,c).call(this);return}o(this,r,U).call(this);let t=e.length,s=()=>{--t>0||(n(),o(this,r,c).call(this))},h=setTimeout(()=>{n(),o(this,r,c).call(this)},ne),n=()=>{this.waiting=null,clearTimeout(h);for(let a of e)a.removeEventListener("load",s),a.removeEventListener("error",s)};this.waiting=n;for(let a of e)a.addEventListener("load",s),a.addEventListener("error",s)},c=function(){this.destroyed||(this.refresh(),this.period>0&&!this.paused&&(this.read(window.innerHeight),this.write(0,z(this.options.scroller))))},U=function(){N||(N=!0,console.warn("wake-marquee: an item holds no width until it loads, so the loop period is not measurable yet and the row is waiting. Give each <img> its width and height attributes, or an aspect-ratio, and it starts immediately."))},B=function(){let e=this.element;this.intersection=new IntersectionObserver(t=>{for(let s of t)this.visible=s.isIntersecting,e.toggleAttribute("data-wake-active",s.isIntersecting),s.isIntersecting&&!this.measured&&o(this,r,v).call(this);d()},{rootMargin:re,threshold:0}),this.intersection.observe(e),this.resize=new ResizeObserver(()=>{this.measured&&this.refresh()}),this.resize.observe(this.lane),this.resize.observe(e),this.options.pauseOnHover&&matchMedia("(hover: hover)").matches&&(this.onEnter=()=>{this.hovered=!0},this.onLeave=()=>{this.hovered=!1,d()},e.addEventListener("pointerenter",this.onEnter),e.addEventListener("pointerleave",this.onLeave)),this.options.respectMotionPreference&&typeof matchMedia=="function"&&(this.motionQuery=matchMedia("(prefers-reduced-motion: reduce)"),this.onMotionChange=()=>o(this,r,k).call(this),this.motionQuery.addEventListener("change",this.onMotionChange))},k=function(){if(!this.options.respectMotionPreference){d();return}R()?(this.paused=!0,o(this,r,_).call(this)):(this.paused=!1,d())},_=function(){this.offset=0,this.wakePx=0,this.aligned=!1;for(let e of this.lanes)e.style.transform="";this.track.style.transform=""},G=function(){if(L)return;let e=this.lane.firstElementChild;if(!e)return;let t=getComputedStyle(e),s=t.getPropertyValue("--wake-gap").trim();!((s===""?32:parseFloat(s))>0)||parseFloat(t.marginInlineEnd)!==0||(L=!0,console.warn("wake-marquee: the items have no spacing. An unlayered CSS reset outranks @layer wake-marquee whatever the specificity says. Put the reset in a layer and name the order first: @layer reset, wake-marquee;"))},Q=function(){this.aligned=!0;let e=this.amplitude-this.wakePx;this.offset=(e%this.period+this.period)%this.period},y=function(){return!this.destroyed&&!this.paused&&this.visible&&this.period>0};function fe(i){let e=l===0?0:q(i-l);l=i;let t=window.innerHeight;for(let s of u)s.options.reverse&&ue(s.options.scroller),s.read(t);for(let s of u)s.write(e,s.options.reverse?z(s.options.scroller):1);w=0,d()}function d(){if(w!==0)return;let i=!1;for(let e of u)if(!e.destroyed&&!e.paused&&e.visible){i=!0;break}if(!i){l=0;return}w=requestAnimationFrame(fe)}function f({root:i=document,defaults:e={}}={}){let t=[];for(let s of i.querySelectorAll(`[${I}]`))de(s)||t.push(new g(s,{...e,...ce(s)}));for(let s of t)s.prime();return t}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>f(),{once:!0}):f());return X(pe);})();
|
package/dist/wake-marquee.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var y=i=>{throw TypeError(i)};var j=(i,e,t)=>e.has(i)||y("Cannot "+t);var b=(i,e,t)=>e.has(i)?y("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(i):e.set(i,t);var n=(i,e,t)=>(j(i,e,"access private method"),t);function V(i,e,t){return i<e?e:i>t?t:i}function E(i,e,t=1){return!(e>0)||!Number.isFinite(i)?1:Math.ceil(i/e)+1+t}function x(i,e){if(typeof i=="number")return i;let t=parseFloat(i);return Number.isFinite(t)&&e>0?t*e/100:0}function C(i,e,t,s){return s>0?((i+e*t)%s+s)%s:0}function M(i,e,t,s){return i+(e-i)*(1-Math.exp(-t*s))}function S(i,e,t){let s=t+e;return s>0?V((t-i)/s,0,1):0}function A(i,e,t){return-t*e*(1-2*i)}function q(i,e=.1){return!Number.isFinite(i)||i<=0?0:Math.min(i/1e3,e)}var K=Object.freeze({direction:"left",speed:60,wake:8,reverse:!0,ease:5,gap:null,fade:!1,pauseOnHover:!1,scroller:null,respectMotionPreference:!0}),N="data-wake-marquee",Y="wake-lane",O="wake-track",J=1,W="200px 0px",X=3e3,Z=/^\d*\.?\d+%$/;function ee(i){return typeof i=="string"?Z.test(i):Number.isFinite(i)&&i>=0}function T(i){return i.firstElementChild?.classList.contains(O)===!0}var u=new Set,p=new WeakMap,m=new Map,w=0,l=0,F=!1,P=!1;function L(i){return i===window?window.scrollY:i.scrollTop}function te(i){let e=m.get(i);if(!e){m.set(i,{last:L(i),sign:1});return}let t=L(i);Math.abs(t-e.last)>.5&&(e.sign=t>e.last?1:-1,e.last=t)}function I(i){return m.get(i)?.sign??1}function R(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function ie(i={}){let e={...K};for(let[t,s]of Object.entries(i))s!==void 0&&(e[t]=s);if(e.direction!=="left"&&e.direction!=="right")throw new RangeError(`wake-marquee: direction must be "left" or "right", received "${e.direction}"`);if(typeof e.speed=="string"&&(e.speed=e.speed.trim()),!ee(e.speed)){let t=typeof e.speed=="string"?`"${e.speed}"`:e.speed;throw new RangeError(`wake-marquee: speed must be a non-negative number of pixels per second, or a percentage of the container width per second such as "8%", received ${t}`)}if(!Number.isFinite(e.wake)||e.wake<0)throw new RangeError(`wake-marquee: wake must be a non-negative number, received ${e.wake}`);if(!Number.isFinite(e.ease)||e.ease<=0)throw new RangeError(`wake-marquee: ease must be a positive number, received ${e.ease}`);return e.scroller=e.scroller??(typeof window>"u"?null:window),e}function se(i){let e=i.dataset,t={};if(e.wakeDirection&&(t.direction=e.wakeDirection),e.wakeSpeed){let s=e.wakeSpeed.trim();t.speed=Number.isFinite(Number(s))?Number(s):s}return e.wake&&(t.wake=Number(e.wake)),e.wakeEase&&(t.ease=Number(e.wakeEase)),e.wakeGap&&(t.gap=e.wakeGap),e.wakeFade&&(t.fade=e.wakeFade),e.wakePauseOnHover!==void 0&&(t.pauseOnHover=!0),e.wakeReverse!==void 0&&(t.reverse=e.wakeReverse!=="false"),t}var r,z,$,D,g,c,B,H,v,U,_,G,k,f=class{constructor(e,t){b(this,r);this.element=e,this.options=ie(t),this.dirSign=this.options.direction==="right"?1:-1,this.dirFactor=this.dirSign,this.offset=0,this.period=0,this.speedPx=0,this.wakePx=0,this.amplitude=0,this.aligned=!1,this.waiting=null,this.visible=!1,this.measured=!1,this.paused=!1,this.hovered=!1,this.destroyed=!1,this.status="",n(this,r,z).call(this),n(this,r,H).call(this),u.add(this),p.set(e,this),n(this,r,v).call(this)}prime(){if(this.destroyed||this.measured)return this;let e=window.innerHeight,t=this.element.getBoundingClientRect(),s=200;return t.bottom<-s||t.top>e+s?this:(this.visible=!0,this.element.toggleAttribute("data-wake-active",!0),n(this,r,g).call(this),this)}refresh(){if(this.destroyed)return;let e=this.lane.getBoundingClientRect().width;if(!(e>0))return;this.period>0&&e!==this.period&&(this.offset=this.offset/this.period*e),this.measured||n(this,r,_).call(this),this.period=e,this.measured=!0,n(this,r,$).call(this);let t=E(this.track.getBoundingClientRect().width,e,J);for(;this.lanes.length<t;){let s=this.lane.cloneNode(!0);s.inert=!0,s.setAttribute("aria-hidden","true"),"inert"in s||s.querySelectorAll("a, button, input, select, textarea, [tabindex]").forEach(h=>h.setAttribute("tabindex","-1")),s.querySelectorAll('img[loading="lazy"]').forEach(h=>h.setAttribute("loading","eager")),s.style.transform=this.lane.style.transform,this.track.appendChild(s),this.lanes.push(s)}for(;this.lanes.length>t;)this.lanes.pop()?.remove();d()}read(e){if(!n(this,r,k).call(this))return;let t=this.element.getBoundingClientRect(),s=S(t.top,t.height,e);this.amplitude=this.options.wake*t.width/100,this.wakePx=A(s,this.amplitude,this.dirSign),this.speedPx=x(this.options.speed,t.width)}write(e,t){if(!n(this,r,k).call(this))return;this.aligned||n(this,r,G).call(this);let s=this.hovered?0:this.options.reverse?this.dirSign*t:this.dirSign;this.dirFactor=M(this.dirFactor,s,e,this.options.ease),this.offset=C(this.offset,this.dirFactor*this.speedPx,e,this.period);let o=`translate3d(${(this.offset-this.period).toFixed(2)}px, 0, 0)`;for(let Q of this.lanes)Q.style.transform=o;this.track.style.transform=`translate3d(${this.wakePx.toFixed(2)}px, 0, 0)`;let a=t===1?"forward":"reversed";a!==this.status&&(this.status=a,this.element.setAttribute("data-wake-travel",a))}play(){return this.destroyed?this:this.options.respectMotionPreference&&R()?this:(this.paused=!1,d(),this)}pause(){return this.paused=!0,this}destroy(){if(!this.destroyed){this.destroyed=!0,u.delete(this),p.delete(this.element),this.waiting?.(),this.intersection?.disconnect(),this.resize?.disconnect(),this.onEnter&&this.element.removeEventListener("pointerenter",this.onEnter),this.onLeave&&this.element.removeEventListener("pointerleave",this.onLeave),this.motionQuery&&this.onMotionChange&&this.motionQuery.removeEventListener("change",this.onMotionChange);for(let e of this.lanes.slice(1))e.remove();for(;this.lane.firstChild;)this.element.appendChild(this.lane.firstChild);this.track.remove(),this.element.removeAttribute("data-wake-travel"),this.element.removeAttribute("data-wake-active");for(let e of this.undo)e();this.undo=[],this.lanes=[]}}};r=new WeakSet,z=function(){let e=this.element,t=e.ownerDocument;for(this.track=t.createElement("div"),this.track.className=O,this.lane=t.createElement("div"),this.lane.className=Y;e.firstChild;)this.lane.appendChild(e.firstChild);this.track.appendChild(this.lane),e.appendChild(this.track),this.lanes=[this.lane],this.undo=[];let s=(o,a)=>{e.hasAttribute(o)||(e.setAttribute(o,a),this.undo.push(()=>e.removeAttribute(o)))},h=(o,a)=>{e.style.getPropertyValue(o)||(e.style.setProperty(o,a),this.undo.push(()=>e.style.removeProperty(o)))};s(N,""),this.options.gap&&h("--wake-gap",this.options.gap),this.options.fade&&(h("--wake-fade",this.options.fade),s("data-wake-fade",""))},$=function(){this.track.style.marginInlineStart=`-${this.options.wake}%`,this.track.style.width=`${100+this.options.wake*2}%`},D=function(){let e=[];for(let t of this.lane.querySelectorAll("img"))!t.complete&&t.getBoundingClientRect().width===0&&e.push(t);return e},g=function(){if(this.waiting)return;let e=n(this,r,D).call(this);if(e.length===0){n(this,r,c).call(this);return}n(this,r,B).call(this);let t=e.length,s=()=>{--t>0||(o(),n(this,r,c).call(this))},h=setTimeout(()=>{o(),n(this,r,c).call(this)},X),o=()=>{this.waiting=null,clearTimeout(h);for(let a of e)a.removeEventListener("load",s),a.removeEventListener("error",s)};this.waiting=o;for(let a of e)a.addEventListener("load",s),a.addEventListener("error",s)},c=function(){this.destroyed||(this.refresh(),this.period>0&&!this.paused&&(this.read(window.innerHeight),this.write(0,I(this.options.scroller))))},B=function(){P||(P=!0,console.warn("wake-marquee: an item holds no width until it loads, so the loop period is not measurable yet and the row is waiting. Give each <img> its width and height attributes, or an aspect-ratio, and it starts immediately."))},H=function(){let e=this.element;this.intersection=new IntersectionObserver(t=>{for(let s of t)this.visible=s.isIntersecting,e.toggleAttribute("data-wake-active",s.isIntersecting),s.isIntersecting&&!this.measured&&n(this,r,g).call(this);d()},{rootMargin:W,threshold:0}),this.intersection.observe(e),this.resize=new ResizeObserver(()=>{this.measured&&this.refresh()}),this.resize.observe(this.lane),this.resize.observe(e),this.options.pauseOnHover&&matchMedia("(hover: hover)").matches&&(this.onEnter=()=>{this.hovered=!0},this.onLeave=()=>{this.hovered=!1,d()},e.addEventListener("pointerenter",this.onEnter),e.addEventListener("pointerleave",this.onLeave)),this.options.respectMotionPreference&&typeof matchMedia=="function"&&(this.motionQuery=matchMedia("(prefers-reduced-motion: reduce)"),this.onMotionChange=()=>n(this,r,v).call(this),this.motionQuery.addEventListener("change",this.onMotionChange))},v=function(){if(!this.options.respectMotionPreference){d();return}R()?(this.paused=!0,n(this,r,U).call(this)):(this.paused=!1,d())},U=function(){this.offset=0,this.wakePx=0,this.aligned=!1;for(let e of this.lanes)e.style.transform="";this.track.style.transform=""},_=function(){if(F)return;let e=this.lane.firstElementChild;if(!e)return;let t=getComputedStyle(e),s=t.getPropertyValue("--wake-gap").trim();!((s===""?32:parseFloat(s))>0)||parseFloat(t.marginInlineEnd)!==0||(F=!0,console.warn("wake-marquee: the items have no spacing. An unlayered CSS reset outranks @layer wake-marquee whatever the specificity says. Put the reset in a layer and name the order first: @layer reset, wake-marquee;"))},G=function(){this.aligned=!0;let e=this.amplitude-this.wakePx;this.offset=(e%this.period+this.period)%this.period},k=function(){return!this.destroyed&&!this.paused&&this.visible&&this.period>0};function re(i){let e=l===0?0:q(i-l);l=i;let t=window.innerHeight;for(let s of u)s.options.reverse&&te(s.options.scroller),s.read(t);for(let s of u)s.write(e,s.options.reverse?I(s.options.scroller):1);w=0,d()}function d(){if(w!==0)return;let i=!1;for(let e of u)if(!e.destroyed&&!e.paused&&e.visible){i=!0;break}if(!i){l=0;return}w=requestAnimationFrame(re)}function de(i,e={}){if(!i||i.nodeType!==1)throw new TypeError("wake-marquee: createMarquee expects an element");if(T(i))throw new Error("wake-marquee: this element is already a marquee, call destroy() first");return new f(i,e).prime()}function ue({root:i=document,defaults:e={}}={}){let t=[];for(let s of i.querySelectorAll(`[${N}]`))T(s)||t.push(new f(s,{...e,...se(s)}));for(let s of t)s.prime();return t}function le(i){return i&&p.get(i)||null}export{f as Marquee,de as createMarquee,K as defaults,le as getMarquee,ue as initMarquees,ie as normalizeOptions,se as readOptions};
|
|
1
|
+
var y=i=>{throw TypeError(i)};var j=(i,e,t)=>e.has(i)||y("Cannot "+t);var b=(i,e,t)=>e.has(i)?y("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(i):e.set(i,t);var o=(i,e,t)=>(j(i,e,"access private method"),t);function V(i,e,t){return i<e?e:i>t?t:i}function E(i,e,t=1){return!(e>0)||!Number.isFinite(i)?1:Math.ceil(i/e)+1+t}function x(i,e){if(typeof i=="number")return i;let t=parseFloat(i);return Number.isFinite(t)&&e>0?t*e/100:0}function S(i,e,t,s){return s>0?((i+e*t)%s+s)%s:0}function C(i,e,t,s){return i+(e-i)*(1-Math.exp(-t*s))}function M(i,e,t){let s=t+e;return s>0?V((t-i)/s,0,1):0}function A(i,e,t){return-t*e*(1-2*i)}function F(i,e=.1){return!Number.isFinite(i)||i<=0?0:Math.min(i/1e3,e)}var W=Object.freeze({direction:"left",speed:60,wake:8,reverse:!0,ease:5,gap:null,fade:!1,pauseOnHover:!1,scroller:null,respectMotionPreference:!0}),O="data-wake-marquee",K="wake-lane",R="wake-track",Y=1,J="200px 0px",X=3e3,Z=/^\d*\.?\d+%$/;function ee(i){return typeof i=="string"?Z.test(i):Number.isFinite(i)&&i>=0}function te(i){return parseFloat(getComputedStyle(i).width)}function I(i){return i.firstElementChild?.classList.contains(R)===!0}var l=new Set,p=new WeakMap,m=new Map,w=0,u=0,q=!1,P=!1;function L(i){return i===window?window.scrollY:i.scrollTop}function ie(i){let e=m.get(i);if(!e){m.set(i,{last:L(i),sign:1});return}let t=L(i);Math.abs(t-e.last)>.5&&(e.sign=t>e.last?1:-1,e.last=t)}function T(i){return m.get(i)?.sign??1}function N(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function se(i={}){let e={...W};for(let[t,s]of Object.entries(i))s!==void 0&&(e[t]=s);if(e.direction!=="left"&&e.direction!=="right")throw new RangeError(`wake-marquee: direction must be "left" or "right", received "${e.direction}"`);if(typeof e.speed=="string"&&(e.speed=e.speed.trim()),!ee(e.speed)){let t=typeof e.speed=="string"?`"${e.speed}"`:e.speed;throw new RangeError(`wake-marquee: speed must be a non-negative number of pixels per second, or a percentage of the container width per second such as "8%", received ${t}`)}if(!Number.isFinite(e.wake)||e.wake<0)throw new RangeError(`wake-marquee: wake must be a non-negative number, received ${e.wake}`);if(!Number.isFinite(e.ease)||e.ease<=0)throw new RangeError(`wake-marquee: ease must be a positive number, received ${e.ease}`);return e.scroller=e.scroller??(typeof window>"u"?null:window),e}function re(i){let e=i.dataset,t={};if(e.wakeDirection&&(t.direction=e.wakeDirection),e.wakeSpeed){let s=e.wakeSpeed.trim();t.speed=Number.isFinite(Number(s))?Number(s):s}return e.wake&&(t.wake=Number(e.wake)),e.wakeEase&&(t.ease=Number(e.wakeEase)),e.wakeGap&&(t.gap=e.wakeGap),e.wakeFade&&(t.fade=e.wakeFade),e.wakePauseOnHover!==void 0&&(t.pauseOnHover=!0),e.wakeReverse!==void 0&&(t.reverse=e.wakeReverse!=="false"),t}var r,z,$,D,g,c,H,U,v,B,_,G,k,f=class{constructor(e,t){b(this,r);this.element=e,this.options=se(t),this.dirSign=this.options.direction==="right"?1:-1,this.dirFactor=this.dirSign,this.offset=0,this.period=0,this.speedPx=0,this.wakePx=0,this.amplitude=0,this.width=0,this.aligned=!1,this.waiting=null,this.visible=!1,this.measured=!1,this.paused=!1,this.hovered=!1,this.destroyed=!1,this.status="",o(this,r,z).call(this),o(this,r,U).call(this),l.add(this),p.set(e,this),o(this,r,v).call(this)}prime(){if(this.destroyed||this.measured)return this;let e=window.innerHeight,t=this.element.getBoundingClientRect(),s=200;return t.bottom<-s||t.top>e+s?this:(this.visible=!0,this.element.toggleAttribute("data-wake-active",!0),o(this,r,g).call(this),this)}refresh(){if(this.destroyed)return;let e=te(this.lane);if(!(e>0))return;this.period>0&&e!==this.period&&(this.offset=this.offset/this.period*e),this.measured||o(this,r,_).call(this),this.period=e,this.measured=!0,o(this,r,$).call(this);let t=getComputedStyle(this.track),s=parseFloat(t.width);this.amplitude=-parseFloat(t.marginInlineStart),this.width=s-this.amplitude*2;let h=E(s,e,Y);for(;this.lanes.length<h;){let n=this.lane.cloneNode(!0);n.inert=!0,n.setAttribute("aria-hidden","true"),"inert"in n||n.querySelectorAll("a, button, input, select, textarea, [tabindex]").forEach(a=>a.setAttribute("tabindex","-1")),n.querySelectorAll('img[loading="lazy"]').forEach(a=>a.setAttribute("loading","eager")),n.style.transform=this.lane.style.transform,this.track.appendChild(n),this.lanes.push(n)}for(;this.lanes.length>h;)this.lanes.pop()?.remove();d()}read(e){if(!o(this,r,k).call(this))return;let t=this.element.getBoundingClientRect(),s=M(t.top,t.height,e);this.wakePx=A(s,this.amplitude,this.dirSign),this.speedPx=x(this.options.speed,this.width)}write(e,t){if(!o(this,r,k).call(this))return;this.aligned||o(this,r,G).call(this);let s=this.hovered?0:this.options.reverse?this.dirSign*t:this.dirSign;this.dirFactor=C(this.dirFactor,s,e,this.options.ease),this.offset=S(this.offset,this.dirFactor*this.speedPx,e,this.period);let n=`translate3d(${(this.offset-this.period).toFixed(2)}px, 0, 0)`;for(let Q of this.lanes)Q.style.transform=n;this.track.style.transform=`translate3d(${this.wakePx.toFixed(2)}px, 0, 0)`;let a=t===1?"forward":"reversed";a!==this.status&&(this.status=a,this.element.setAttribute("data-wake-travel",a))}play(){return this.destroyed?this:this.options.respectMotionPreference&&N()?this:(this.paused=!1,d(),this)}pause(){return this.paused=!0,this}destroy(){if(!this.destroyed){this.destroyed=!0,l.delete(this),p.delete(this.element),this.waiting?.(),this.intersection?.disconnect(),this.resize?.disconnect(),this.onEnter&&this.element.removeEventListener("pointerenter",this.onEnter),this.onLeave&&this.element.removeEventListener("pointerleave",this.onLeave),this.motionQuery&&this.onMotionChange&&this.motionQuery.removeEventListener("change",this.onMotionChange);for(let e of this.lanes.slice(1))e.remove();for(;this.lane.firstChild;)this.element.appendChild(this.lane.firstChild);this.track.remove(),this.element.removeAttribute("data-wake-travel"),this.element.removeAttribute("data-wake-active");for(let e of this.undo)e();this.undo=[],this.lanes=[]}}};r=new WeakSet,z=function(){let e=this.element,t=e.ownerDocument;for(this.track=t.createElement("div"),this.track.className=R,this.lane=t.createElement("div"),this.lane.className=K;e.firstChild;)this.lane.appendChild(e.firstChild);this.track.appendChild(this.lane),e.appendChild(this.track),this.lanes=[this.lane],this.undo=[];let s=(n,a)=>{e.hasAttribute(n)||(e.setAttribute(n,a),this.undo.push(()=>e.removeAttribute(n)))},h=(n,a)=>{e.style.getPropertyValue(n)||(e.style.setProperty(n,a),this.undo.push(()=>e.style.removeProperty(n)))};s(O,""),this.options.gap&&h("--wake-gap",this.options.gap),this.options.fade&&(h("--wake-fade",this.options.fade),s("data-wake-fade",""))},$=function(){this.track.style.marginInlineStart=`-${this.options.wake}%`,this.track.style.width=`${100+this.options.wake*2}%`},D=function(){let e=[];for(let t of this.lane.querySelectorAll("img"))!t.complete&&t.getBoundingClientRect().width===0&&e.push(t);return e},g=function(){if(this.waiting)return;let e=o(this,r,D).call(this);if(e.length===0){o(this,r,c).call(this);return}o(this,r,H).call(this);let t=e.length,s=()=>{--t>0||(n(),o(this,r,c).call(this))},h=setTimeout(()=>{n(),o(this,r,c).call(this)},X),n=()=>{this.waiting=null,clearTimeout(h);for(let a of e)a.removeEventListener("load",s),a.removeEventListener("error",s)};this.waiting=n;for(let a of e)a.addEventListener("load",s),a.addEventListener("error",s)},c=function(){this.destroyed||(this.refresh(),this.period>0&&!this.paused&&(this.read(window.innerHeight),this.write(0,T(this.options.scroller))))},H=function(){P||(P=!0,console.warn("wake-marquee: an item holds no width until it loads, so the loop period is not measurable yet and the row is waiting. Give each <img> its width and height attributes, or an aspect-ratio, and it starts immediately."))},U=function(){let e=this.element;this.intersection=new IntersectionObserver(t=>{for(let s of t)this.visible=s.isIntersecting,e.toggleAttribute("data-wake-active",s.isIntersecting),s.isIntersecting&&!this.measured&&o(this,r,g).call(this);d()},{rootMargin:J,threshold:0}),this.intersection.observe(e),this.resize=new ResizeObserver(()=>{this.measured&&this.refresh()}),this.resize.observe(this.lane),this.resize.observe(e),this.options.pauseOnHover&&matchMedia("(hover: hover)").matches&&(this.onEnter=()=>{this.hovered=!0},this.onLeave=()=>{this.hovered=!1,d()},e.addEventListener("pointerenter",this.onEnter),e.addEventListener("pointerleave",this.onLeave)),this.options.respectMotionPreference&&typeof matchMedia=="function"&&(this.motionQuery=matchMedia("(prefers-reduced-motion: reduce)"),this.onMotionChange=()=>o(this,r,v).call(this),this.motionQuery.addEventListener("change",this.onMotionChange))},v=function(){if(!this.options.respectMotionPreference){d();return}N()?(this.paused=!0,o(this,r,B).call(this)):(this.paused=!1,d())},B=function(){this.offset=0,this.wakePx=0,this.aligned=!1;for(let e of this.lanes)e.style.transform="";this.track.style.transform=""},_=function(){if(q)return;let e=this.lane.firstElementChild;if(!e)return;let t=getComputedStyle(e),s=t.getPropertyValue("--wake-gap").trim();!((s===""?32:parseFloat(s))>0)||parseFloat(t.marginInlineEnd)!==0||(q=!0,console.warn("wake-marquee: the items have no spacing. An unlayered CSS reset outranks @layer wake-marquee whatever the specificity says. Put the reset in a layer and name the order first: @layer reset, wake-marquee;"))},G=function(){this.aligned=!0;let e=this.amplitude-this.wakePx;this.offset=(e%this.period+this.period)%this.period},k=function(){return!this.destroyed&&!this.paused&&this.visible&&this.period>0};function ne(i){let e=u===0?0:F(i-u);u=i;let t=window.innerHeight;for(let s of l)s.options.reverse&&ie(s.options.scroller),s.read(t);for(let s of l)s.write(e,s.options.reverse?T(s.options.scroller):1);w=0,d()}function d(){if(w!==0)return;let i=!1;for(let e of l)if(!e.destroyed&&!e.paused&&e.visible){i=!0;break}if(!i){u=0;return}w=requestAnimationFrame(ne)}function le(i,e={}){if(!i||i.nodeType!==1)throw new TypeError("wake-marquee: createMarquee expects an element");if(I(i))throw new Error("wake-marquee: this element is already a marquee, call destroy() first");return new f(i,e).prime()}function ue({root:i=document,defaults:e={}}={}){let t=[];for(let s of i.querySelectorAll(`[${O}]`))I(s)||t.push(new f(s,{...e,...re(s)}));for(let s of t)s.prime();return t}function ce(i){return i&&p.get(i)||null}export{f as Marquee,le as createMarquee,W as defaults,ce as getMarquee,ue as initMarquees,se as normalizeOptions,re as readOptions};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wake-marquee",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "An endless marquee that answers to the scroll: it reverses when the reader scrolls back, and drags against its own direction of travel as it crosses the viewport.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/marquee.js
CHANGED
|
@@ -110,6 +110,36 @@ function isSpeed(value) {
|
|
|
110
110
|
return Number.isFinite(value) && /** @type {number} */ (value) >= 0;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The width an element occupies in its own coordinate system, before any
|
|
115
|
+
* transform between it and the viewport.
|
|
116
|
+
*
|
|
117
|
+
* `getBoundingClientRect()` reports the box after every transform on the
|
|
118
|
+
* element and on its ancestors, and that is the wrong number for every
|
|
119
|
+
* distance the loop is built from. The lanes are translated *inside* that
|
|
120
|
+
* coordinate system, and the track's overhang is a CSS percentage the browser
|
|
121
|
+
* resolves against the untransformed box. A container carrying
|
|
122
|
+
* `rotate(90deg)` therefore reports its own thickness as its width, and the
|
|
123
|
+
* row runs on numbers wrong by its aspect ratio: three lanes where seven cover
|
|
124
|
+
* the track, a period that wraps short, and a wake a fraction of the one that
|
|
125
|
+
* was asked for. Nothing errors, which is what makes it worth a helper.
|
|
126
|
+
*
|
|
127
|
+
* The resolved value of `width` is the used width in layout px: free of the
|
|
128
|
+
* transform, and still sub-pixel exact where `offsetWidth` would round. That
|
|
129
|
+
* matters most for the lane, whose width *is* the loop period: rounded to
|
|
130
|
+
* whole pixels the seam would jump by up to half a pixel on every wrap.
|
|
131
|
+
*
|
|
132
|
+
* An element that is not being rendered resolves to `auto`, which parses as
|
|
133
|
+
* NaN. That is deliberate and load-bearing: it is what keeps refresh() from
|
|
134
|
+
* locking in a period for a row inside a `display: none` ancestor.
|
|
135
|
+
*
|
|
136
|
+
* @param {HTMLElement} el
|
|
137
|
+
* @returns {number} Width in px, or NaN when the element is not rendered.
|
|
138
|
+
*/
|
|
139
|
+
function layoutWidth(el) {
|
|
140
|
+
return parseFloat(getComputedStyle(el).width);
|
|
141
|
+
}
|
|
142
|
+
|
|
113
143
|
/**
|
|
114
144
|
* Has this element already been turned into a marquee?
|
|
115
145
|
*
|
|
@@ -297,8 +327,10 @@ class Marquee {
|
|
|
297
327
|
this.speedPx = 0;
|
|
298
328
|
/** Last written wake displacement in px, kept so a paused frame holds. */
|
|
299
329
|
this.wakePx = 0;
|
|
300
|
-
/** Last measured wake amplitude in px. */
|
|
330
|
+
/** Last measured wake amplitude in px, read back off the overhang. */
|
|
301
331
|
this.amplitude = 0;
|
|
332
|
+
/** Container width in px, in its own coordinate system. */
|
|
333
|
+
this.width = 0;
|
|
302
334
|
/** Has the first frame been lined up with the static row it replaces? */
|
|
303
335
|
this.aligned = false;
|
|
304
336
|
|
|
@@ -632,10 +664,12 @@ class Marquee {
|
|
|
632
664
|
refresh() {
|
|
633
665
|
if (this.destroyed) return;
|
|
634
666
|
|
|
635
|
-
const period = this.lane
|
|
667
|
+
const period = layoutWidth(this.lane);
|
|
636
668
|
// A display:none ancestor, or a lane whose images have not laid out yet,
|
|
637
|
-
// measures
|
|
638
|
-
// callback tries again, rather than locking in a broken period.
|
|
669
|
+
// measures nothing. Bailing leaves `measured` false so the next observer
|
|
670
|
+
// callback tries again, rather than locking in a broken period. It has to
|
|
671
|
+
// stay ahead of the track read below: a hidden track still reports the
|
|
672
|
+
// `120%` and `-10%` it was given, which parse as plausible numbers.
|
|
639
673
|
if (!(period > 0)) return;
|
|
640
674
|
|
|
641
675
|
// The period is the unit the offset is expressed in, so when it changes
|
|
@@ -652,7 +686,19 @@ class Marquee {
|
|
|
652
686
|
this.measured = true;
|
|
653
687
|
this.#setOverhang();
|
|
654
688
|
|
|
655
|
-
|
|
689
|
+
// Read the overhang back rather than working it out a second time. The
|
|
690
|
+
// browser has just resolved that percentage against the container's own
|
|
691
|
+
// box, so this is exactly the reserve the wake is allowed to spend,
|
|
692
|
+
// whatever padding, box-sizing or transform the caller's container
|
|
693
|
+
// carries. Derived twice the two can disagree; read back they cannot.
|
|
694
|
+
const overhang = getComputedStyle(this.track);
|
|
695
|
+
const trackWidth = parseFloat(overhang.width);
|
|
696
|
+
this.amplitude = -parseFloat(overhang.marginInlineStart);
|
|
697
|
+
// The track is the container plus that reserve on either side, so the
|
|
698
|
+
// container falls out of the two numbers already in hand.
|
|
699
|
+
this.width = trackWidth - this.amplitude * 2;
|
|
700
|
+
|
|
701
|
+
const needed = laneCount(trackWidth, period, LANE_BUFFER);
|
|
656
702
|
|
|
657
703
|
while (this.lanes.length < needed) {
|
|
658
704
|
const clone = /** @type {HTMLElement} */ (this.lane.cloneNode(true));
|
|
@@ -725,17 +771,19 @@ class Marquee {
|
|
|
725
771
|
/** Read geometry. Never writes, so it cannot force a layout mid-frame. */
|
|
726
772
|
read(viewport) {
|
|
727
773
|
if (!this.#running()) return;
|
|
774
|
+
// The transformed box, on purpose, and the only place it is wanted: the
|
|
775
|
+
// wake is driven by the element crossing the viewport, so where it is on
|
|
776
|
+
// screen is the honest question. For a rotated row that is its long side,
|
|
777
|
+
// which is right. Every distance the loop travels is read in refresh()
|
|
778
|
+
// instead, in the coordinate system the transforms are written in.
|
|
728
779
|
const rect = this.element.getBoundingClientRect();
|
|
729
780
|
const progress = viewProgress(rect.top, rect.height, viewport);
|
|
730
|
-
// Kept because the first frame's alignment has to undo exactly the
|
|
731
|
-
// overhang the track was given, and that is measured in these same px.
|
|
732
|
-
this.amplitude = (this.options.wake * rect.width) / 100;
|
|
733
781
|
this.wakePx = wakeOffset(progress, this.amplitude, this.dirSign);
|
|
734
|
-
// Resolved here rather than in write()
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
this.speedPx = resolveSpeed(this.options.speed,
|
|
782
|
+
// Resolved here rather than in write() so an option changed at runtime
|
|
783
|
+
// takes effect on the next frame. The width it is a fraction of comes
|
|
784
|
+
// from the last measurement, and a container resized under a running row
|
|
785
|
+
// is still followed: that resize is what the ResizeObserver is for.
|
|
786
|
+
this.speedPx = resolveSpeed(this.options.speed, this.width);
|
|
739
787
|
}
|
|
740
788
|
|
|
741
789
|
/**
|