wake-marquee 0.2.0 → 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 +57 -1
- package/README.md +33 -10
- package/dist/wake-marquee.auto.js +1 -1
- package/dist/wake-marquee.js +1 -1
- package/package.json +1 -1
- package/src/marquee.js +182 -16
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,60 @@ 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
|
+
|
|
41
|
+
## [0.2.1] - 2026-09-01
|
|
42
|
+
|
|
43
|
+
Two ways the row could still rearrange itself in front of the reader
|
|
44
|
+
after it had already started.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- A row of images with no `width` and `height` no longer jerks sideways once
|
|
49
|
+
for every image that arrives. An unloaded, unsized `<img>` lays out zero
|
|
50
|
+
pixels wide, and the lane's width is the loop period, so the row was
|
|
51
|
+
starting on a period made of its gaps and nothing else: eight logos over a
|
|
52
|
+
phone connection measured 384px at the first frame and 1608px a second and a
|
|
53
|
+
half later, and yanked by up to 216px on each step in between. A row whose
|
|
54
|
+
items reserve no space now waits for them and hands over once, which is
|
|
55
|
+
invisible because what stands there meanwhile is the static row that was on
|
|
56
|
+
screen anyway. The wait is capped, so a request that never answers costs a
|
|
57
|
+
late start rather than a row that never runs. It is the safe behaviour and
|
|
58
|
+
not the good one, so it also warns, once per page: the fix is two attributes
|
|
59
|
+
in the markup, and they spare the rest of the page the same layout shift.
|
|
60
|
+
- A lane added by a resize is given the loop's current transform before it is
|
|
61
|
+
attached. `ResizeObserver` is notified after the same frame's
|
|
62
|
+
`requestAnimationFrame` callbacks, so a lane cloned from one was painted once
|
|
63
|
+
at its untransformed position, up to a whole period right of its siblings.
|
|
64
|
+
|
|
11
65
|
## [0.2.0] - 2026-09-01
|
|
12
66
|
|
|
13
67
|
Three things adopting `0.1.0` in a real project turned up.
|
|
@@ -110,6 +164,8 @@ First release.
|
|
|
110
164
|
- Right-to-left writing modes are not supported. Flex rows follow the writing
|
|
111
165
|
direction while the transforms are physical, so the two disagree.
|
|
112
166
|
|
|
113
|
-
[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
|
|
169
|
+
[0.2.1]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.0...v0.2.1
|
|
114
170
|
[0.2.0]: https://github.com/robin-gogolok/wake-marquee/compare/v0.1.0...v0.2.0
|
|
115
171
|
[0.1.0]: https://github.com/robin-gogolok/wake-marquee/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -45,9 +45,9 @@ Write the container and its items. The direct children become the items; the lib
|
|
|
45
45
|
|
|
46
46
|
```html
|
|
47
47
|
<div data-wake-marquee data-wake-speed="55" data-wake="10" data-wake-fade="6rem">
|
|
48
|
-
<img src="/logos/a.svg" alt="Nordlicht">
|
|
49
|
-
<img src="/logos/b.svg" alt="Kvist">
|
|
50
|
-
<img src="/logos/c.svg" alt="Halden & Co">
|
|
48
|
+
<img src="/logos/a.svg" alt="Nordlicht" width="150" height="48">
|
|
49
|
+
<img src="/logos/b.svg" alt="Kvist" width="110" height="48">
|
|
50
|
+
<img src="/logos/c.svg" alt="Halden & Co" width="175" height="48">
|
|
51
51
|
</div>
|
|
52
52
|
```
|
|
53
53
|
|
|
@@ -76,7 +76,7 @@ const row = createMarquee(document.querySelector('#logos'), {
|
|
|
76
76
|
import WakeMarquee from 'wake-marquee/astro'
|
|
77
77
|
---
|
|
78
78
|
<WakeMarquee speed="8%" wake={10} fade="6rem" aria-label="Brands we stock">
|
|
79
|
-
{brands.map((b) => <img src={b.logo} alt={b.name} />)}
|
|
79
|
+
{brands.map((b) => <img src={b.logo} alt={b.name} width={b.width} height={48} />)}
|
|
80
80
|
</WakeMarquee>
|
|
81
81
|
```
|
|
82
82
|
|
|
@@ -199,10 +199,16 @@ Same for `data-wake-fade` and `--wake-fade`. The library leaves a property
|
|
|
199
199
|
that is already set alone, so there is no conflict. `wake-marquee/astro` does
|
|
200
200
|
this for you.
|
|
201
201
|
|
|
202
|
-
**Give images `width` and `height`.** An unsized image
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
**Give images `width` and `height`.** An unsized image is zero pixels wide
|
|
203
|
+
until it decodes, and the lane width is the loop period. A row of eight
|
|
204
|
+
unsized logos measures its gaps and nothing else at the first frame, then
|
|
205
|
+
grows into its real width one logo at a time.
|
|
206
|
+
|
|
207
|
+
The library will not start on a measurement like that: a row whose items
|
|
208
|
+
reserve no space waits for them, keeps standing as the static row the reader
|
|
209
|
+
was already looking at, and starts once. That is the safe behaviour, not the
|
|
210
|
+
good one, and it says so in the console. With the attributes there is nothing
|
|
211
|
+
to wait for and the row starts immediately.
|
|
206
212
|
|
|
207
213
|
## API
|
|
208
214
|
|
|
@@ -355,6 +361,23 @@ The same rule, the other way round. Declare the layer order before the imports,
|
|
|
355
361
|
|
|
356
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.
|
|
357
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
|
+
|
|
358
381
|
## Accessibility
|
|
359
382
|
|
|
360
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.
|
|
@@ -386,9 +409,9 @@ There is no build-time or CSS-only version of this, and there cannot be one whil
|
|
|
386
409
|
|
|
387
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.
|
|
388
411
|
|
|
389
|
-
###
|
|
412
|
+
### Left to right
|
|
390
413
|
|
|
391
|
-
|
|
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.
|
|
392
415
|
|
|
393
416
|
### One measurement, one period
|
|
394
417
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var WakeMarquee=(()=>{var l=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var y=i=>{throw TypeError(i)};var j=(i,e)=>{for(var t in e)l(i,t,{get:e[t],enumerable:!0})},V=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of G(e))!U.call(i,n)&&n!==t&&l(i,n,{get:()=>e[n],enumerable:!(s=_(e,n))||s.enumerable});return i};var K=i=>V(l({},"__esModule",{value:!0}),i);var Y=(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)=>(Y(i,e,"access private method"),t);var he={};j(he,{initMarquees:()=>f});function J(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 E(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?J((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 W=Object.freeze({direction:"left",speed:60,wake:8,reverse:!0,ease:5,gap:null,fade:!1,pauseOnHover:!1,scroller:null,respectMotionPreference:!0}),R="data-wake-marquee",X="wake-lane",O="wake-track",Z=1,ee="200px 0px",te=/^\d*\.?\d+%$/;function ie(i){return typeof i=="string"?te.test(i):Number.isFinite(i)&&i>=0}function se(i){return i.firstElementChild?.classList.contains(O)===!0}var u=new Set,F=new WeakMap,p=new Map,m=0,c=0,P=!1;function L(i){return i===window?window.scrollY:i.scrollTop}function re(i){let e=p.get(i);if(!e){p.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){return p.get(i)?.sign??1}function N(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function ne(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()),!ie(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 oe(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,I,T,g,z,v,D,H,B,k,w=class{constructor(e,t){b(this,r);this.element=e,this.options=ne(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.visible=!1,this.measured=!1,this.paused=!1,this.hovered=!1,this.destroyed=!1,this.status="",o(this,r,I).call(this),o(this,r,z).call(this),u.add(this),F.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=this.lane.getBoundingClientRect().width;if(!(e>0))return;this.period>0&&e!==this.period&&(this.offset=this.offset/this.period*e),this.measured||o(this,r,H).call(this),this.period=e,this.measured=!0,o(this,r,T).call(this);let t=x(this.track.getBoundingClientRect().width,e,Z);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(n=>n.setAttribute("tabindex","-1")),s.querySelectorAll('img[loading="lazy"]').forEach(n=>n.setAttribute("loading","eager")),this.track.appendChild(s),this.lanes.push(s)}for(;this.lanes.length>t;)this.lanes.pop()?.remove();d()}read(e){if(!o(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=E(this.options.speed,t.width)}write(e,t){if(!o(this,r,k).call(this))return;this.aligned||o(this,r,B).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 a=`translate3d(${(this.offset-this.period).toFixed(2)}px, 0, 0)`;for(let Q of this.lanes)Q.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&&N()?this:(this.paused=!1,d(),this)}pause(){return this.paused=!0,this}destroy(){if(!this.destroyed){this.destroyed=!0,u.delete(this),F.delete(this.element),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,I=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=X;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)))},n=(a,h)=>{e.style.getPropertyValue(a)||(e.style.setProperty(a,h),this.undo.push(()=>e.style.removeProperty(a)))};s(R,""),this.options.gap&&n("--wake-gap",this.options.gap),this.options.fade&&(n("--wake-fade",this.options.fade),s("data-wake-fade",""))},T=function(){this.track.style.marginInlineStart=`-${this.options.wake}%`,this.track.style.width=`${100+this.options.wake*2}%`},g=function(){this.refresh(),this.period>0&&!this.paused&&(this.read(window.innerHeight),this.write(0,$(this.options.scroller)))},z=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:ee,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,D).call(this)):(this.paused=!1,d())},D=function(){this.offset=0,this.wakePx=0,this.aligned=!1;for(let e of this.lanes)e.style.transform="";this.track.style.transform=""},H=function(){if(P)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||(P=!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;"))},B=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 ae(i){let e=c===0?0:q(i-c);c=i;let t=window.innerHeight;for(let s of u)s.options.reverse&&re(s.options.scroller),s.read(t);for(let s of u)s.write(e,s.options.reverse?$(s.options.scroller):1);m=0,d()}function d(){if(m!==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}m=requestAnimationFrame(ae)}function f({root:i=document,defaults:e={}}={}){let t=[];for(let s of i.querySelectorAll(`[${R}]`))se(s)||t.push(new w(s,{...e,...oe(s)}));for(let s of t)s.prime();return t}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>f(),{once:!0}):f());return K(he);})();
|
|
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 k=t=>{throw TypeError(t)};var Q=(t,e,i)=>e.has(t)||k("Cannot "+i);var y=(t,e,i)=>e.has(t)?k("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,i);var n=(t,e,i)=>(Q(t,e,"access private method"),i);function _(t,e,i){return t<e?e:t>i?i:t}function b(t,e,i=1){return!(e>0)||!Number.isFinite(t)?1:Math.ceil(t/e)+1+i}function x(t,e){if(typeof t=="number")return t;let i=parseFloat(t);return Number.isFinite(i)&&e>0?i*e/100:0}function E(t,e,i,s){return s>0?((t+e*i)%s+s)%s:0}function C(t,e,i,s){return t+(e-t)*(1-Math.exp(-i*s))}function S(t,e,i){let s=i+e;return s>0?_((i-t)/s,0,1):0}function M(t,e,i){return-i*e*(1-2*t)}function A(t,e=.1){return!Number.isFinite(t)||t<=0?0:Math.min(t/1e3,e)}var G=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",U="wake-lane",R="wake-track",j=1,V="200px 0px",K=/^\d*\.?\d+%$/;function Y(t){return typeof t=="string"?K.test(t):Number.isFinite(t)&&t>=0}function L(t){return t.firstElementChild?.classList.contains(R)===!0}var c=new Set,l=new WeakMap,p=new Map,m=0,u=0,F=!1;function P(t){return t===window?window.scrollY:t.scrollTop}function J(t){let e=p.get(t);if(!e){p.set(t,{last:P(t),sign:1});return}let i=P(t);Math.abs(i-e.last)>.5&&(e.sign=i>e.last?1:-1,e.last=i)}function O(t){return p.get(t)?.sign??1}function q(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function W(t={}){let e={...G};for(let[i,s]of Object.entries(t))s!==void 0&&(e[i]=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()),!Y(e.speed)){let i=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 ${i}`)}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 X(t){let e=t.dataset,i={};if(e.wakeDirection&&(i.direction=e.wakeDirection),e.wakeSpeed){let s=e.wakeSpeed.trim();i.speed=Number.isFinite(Number(s))?Number(s):s}return e.wake&&(i.wake=Number(e.wake)),e.wakeEase&&(i.ease=Number(e.wakeEase)),e.wakeGap&&(i.gap=e.wakeGap),e.wakeFade&&(i.fade=e.wakeFade),e.wakePauseOnHover!==void 0&&(i.pauseOnHover=!0),e.wakeReverse!==void 0&&(i.reverse=e.wakeReverse!=="false"),i}var r,$,I,w,T,g,z,D,H,v,f=class{constructor(e,i){y(this,r);this.element=e,this.options=W(i),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.visible=!1,this.measured=!1,this.paused=!1,this.hovered=!1,this.destroyed=!1,this.status="",n(this,r,$).call(this),n(this,r,T).call(this),c.add(this),l.set(e,this),n(this,r,g).call(this)}prime(){if(this.destroyed||this.measured)return this;let e=window.innerHeight,i=this.element.getBoundingClientRect(),s=200;return i.bottom<-s||i.top>e+s?this:(this.visible=!0,this.element.toggleAttribute("data-wake-active",!0),n(this,r,w).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,D).call(this),this.period=e,this.measured=!0,n(this,r,I).call(this);let i=b(this.track.getBoundingClientRect().width,e,j);for(;this.lanes.length<i;){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")),this.track.appendChild(s),this.lanes.push(s)}for(;this.lanes.length>i;)this.lanes.pop()?.remove();d()}read(e){if(!n(this,r,v).call(this))return;let i=this.element.getBoundingClientRect(),s=S(i.top,i.height,e);this.amplitude=this.options.wake*i.width/100,this.wakePx=M(s,this.amplitude,this.dirSign),this.speedPx=x(this.options.speed,i.width)}write(e,i){if(!n(this,r,v).call(this))return;this.aligned||n(this,r,H).call(this);let s=this.hovered?0:this.options.reverse?this.dirSign*i:this.dirSign;this.dirFactor=C(this.dirFactor,s,e,this.options.ease),this.offset=E(this.offset,this.dirFactor*this.speedPx,e,this.period);let a=`translate3d(${(this.offset-this.period).toFixed(2)}px, 0, 0)`;for(let B of this.lanes)B.style.transform=a;this.track.style.transform=`translate3d(${this.wakePx.toFixed(2)}px, 0, 0)`;let h=i===1?"forward":"reversed";h!==this.status&&(this.status=h,this.element.setAttribute("data-wake-travel",h))}play(){return this.destroyed?this:this.options.respectMotionPreference&&q()?this:(this.paused=!1,d(),this)}pause(){return this.paused=!0,this}destroy(){if(!this.destroyed){this.destroyed=!0,c.delete(this),l.delete(this.element),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,i=e.ownerDocument;for(this.track=i.createElement("div"),this.track.className=R,this.lane=i.createElement("div"),this.lane.className=U;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(N,""),this.options.gap&&o("--wake-gap",this.options.gap),this.options.fade&&(o("--wake-fade",this.options.fade),s("data-wake-fade",""))},I=function(){this.track.style.marginInlineStart=`-${this.options.wake}%`,this.track.style.width=`${100+this.options.wake*2}%`},w=function(){this.refresh(),this.period>0&&!this.paused&&(this.read(window.innerHeight),this.write(0,O(this.options.scroller)))},T=function(){let e=this.element;this.intersection=new IntersectionObserver(i=>{for(let s of i)this.visible=s.isIntersecting,e.toggleAttribute("data-wake-active",s.isIntersecting),s.isIntersecting&&!this.measured&&n(this,r,w).call(this);d()},{rootMargin:V,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,g).call(this),this.motionQuery.addEventListener("change",this.onMotionChange))},g=function(){if(!this.options.respectMotionPreference){d();return}q()?(this.paused=!0,n(this,r,z).call(this)):(this.paused=!1,d())},z=function(){this.offset=0,this.wakePx=0,this.aligned=!1;for(let e of this.lanes)e.style.transform="";this.track.style.transform=""},D=function(){if(F)return;let e=this.lane.firstElementChild;if(!e)return;let i=getComputedStyle(e),s=i.getPropertyValue("--wake-gap").trim();!((s===""?32:parseFloat(s))>0)||parseFloat(i.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;"))},H=function(){this.aligned=!0;let e=this.amplitude-this.wakePx;this.offset=(e%this.period+this.period)%this.period},v=function(){return!this.destroyed&&!this.paused&&this.visible&&this.period>0};function Z(t){let e=u===0?0:A(t-u);u=t;let i=window.innerHeight;for(let s of c)s.options.reverse&&J(s.options.scroller),s.read(i);for(let s of c)s.write(e,s.options.reverse?O(s.options.scroller):1);m=0,d()}function d(){if(m!==0)return;let t=!1;for(let e of c)if(!e.destroyed&&!e.paused&&e.visible){t=!0;break}if(!t){u=0;return}m=requestAnimationFrame(Z)}function re(t,e={}){if(!t||t.nodeType!==1)throw new TypeError("wake-marquee: createMarquee expects an element");if(L(t))throw new Error("wake-marquee: this element is already a marquee, call destroy() first");return new f(t,e).prime()}function ne({root:t=document,defaults:e={}}={}){let i=[];for(let s of t.querySelectorAll(`[${N}]`))L(s)||i.push(new f(s,{...e,...X(s)}));for(let s of i)s.prime();return i}function oe(t){return t&&l.get(t)||null}export{f as Marquee,re as createMarquee,G as defaults,oe as getMarquee,ne as initMarquees,W as normalizeOptions,X 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
|
@@ -84,6 +84,13 @@ const LANE_BUFFER = 1;
|
|
|
84
84
|
/** How far outside the viewport an instance starts running, in px. */
|
|
85
85
|
const ROOT_MARGIN = '200px 0px';
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* How long a row waits for an image that reserves no space of its own before
|
|
89
|
+
* it gives up and starts anyway, in ms. Generous on purpose: standing still a
|
|
90
|
+
* moment longer is invisible, and starting early is the bug this guards.
|
|
91
|
+
*/
|
|
92
|
+
const MEDIA_TIMEOUT = 3000;
|
|
93
|
+
|
|
87
94
|
/** The relative form of `speed`: `'8%'` of the container width, per second. */
|
|
88
95
|
const PERCENT = /^\d*\.?\d+%$/;
|
|
89
96
|
|
|
@@ -103,6 +110,36 @@ function isSpeed(value) {
|
|
|
103
110
|
return Number.isFinite(value) && /** @type {number} */ (value) >= 0;
|
|
104
111
|
}
|
|
105
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
|
+
|
|
106
143
|
/**
|
|
107
144
|
* Has this element already been turned into a marquee?
|
|
108
145
|
*
|
|
@@ -138,6 +175,9 @@ let lastFrame = 0;
|
|
|
138
175
|
/** Warned about collapsed spacing once? A global reset breaks every row. */
|
|
139
176
|
let warnedSpacing = false;
|
|
140
177
|
|
|
178
|
+
/** Warned about an image with no reserved space once? Same reasoning. */
|
|
179
|
+
let warnedUnsized = false;
|
|
180
|
+
|
|
141
181
|
/**
|
|
142
182
|
* Read the scroll offset of either the window or an overflow container.
|
|
143
183
|
* @param {Window | HTMLElement} scroller
|
|
@@ -287,11 +327,20 @@ class Marquee {
|
|
|
287
327
|
this.speedPx = 0;
|
|
288
328
|
/** Last written wake displacement in px, kept so a paused frame holds. */
|
|
289
329
|
this.wakePx = 0;
|
|
290
|
-
/** Last measured wake amplitude in px. */
|
|
330
|
+
/** Last measured wake amplitude in px, read back off the overhang. */
|
|
291
331
|
this.amplitude = 0;
|
|
332
|
+
/** Container width in px, in its own coordinate system. */
|
|
333
|
+
this.width = 0;
|
|
292
334
|
/** Has the first frame been lined up with the static row it replaces? */
|
|
293
335
|
this.aligned = false;
|
|
294
336
|
|
|
337
|
+
/**
|
|
338
|
+
* @type {(() => void) | null} Cancels the wait for an image that has not
|
|
339
|
+
* reserved its space yet, while there is one. Doubles as the guard that
|
|
340
|
+
* keeps a second observer callback from starting a second wait.
|
|
341
|
+
*/
|
|
342
|
+
this.waiting = null;
|
|
343
|
+
|
|
295
344
|
this.visible = false;
|
|
296
345
|
this.measured = false;
|
|
297
346
|
this.paused = false;
|
|
@@ -393,11 +442,85 @@ class Marquee {
|
|
|
393
442
|
}
|
|
394
443
|
|
|
395
444
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
445
|
+
* The images in the lane that will move the loop period when they arrive.
|
|
446
|
+
*
|
|
447
|
+
* An `<img>` with no width and height in the markup lays out zero pixels
|
|
448
|
+
* wide until it has decoded, and the lane's width *is* the period. Measure
|
|
449
|
+
* now and the row runs on a period that is a fraction of its real one, then
|
|
450
|
+
* grows into it a logo at a time: eight logos, eight sideways yanks, spread
|
|
451
|
+
* over however long the slowest of them takes. A row of eight unsized
|
|
452
|
+
* wordmarks measures 384px at the first frame and 1608px a second and a
|
|
453
|
+
* half later, and jerks by up to 216px on each step in between.
|
|
454
|
+
*
|
|
455
|
+
* Deliberately a measurement rather than a plain `!complete`. An image that
|
|
456
|
+
* already holds its own box cannot change the period whatever it turns out
|
|
457
|
+
* to contain, so markup that reserves its space needs no waiting at all and
|
|
458
|
+
* keeps the single synchronous handover it has today.
|
|
459
|
+
*
|
|
460
|
+
* @returns {HTMLImageElement[]}
|
|
461
|
+
*/
|
|
462
|
+
#unsizedImages() {
|
|
463
|
+
const pending = [];
|
|
464
|
+
for (const img of this.lane.querySelectorAll('img')) {
|
|
465
|
+
if (!img.complete && img.getBoundingClientRect().width === 0) pending.push(img);
|
|
466
|
+
}
|
|
467
|
+
return pending;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Take the row from its static state to its running one, once the period it
|
|
472
|
+
* would measure is the period it is going to keep.
|
|
473
|
+
*
|
|
474
|
+
* Held back while the lane still contains images that reserve no space. The
|
|
475
|
+
* wait costs nothing anybody can see: what stands there in the meantime is
|
|
476
|
+
* the static clipped row, which is what was on screen anyway, and it now
|
|
477
|
+
* becomes a moving row exactly once instead of stepping into place.
|
|
399
478
|
*/
|
|
400
479
|
#activate() {
|
|
480
|
+
if (this.waiting) return;
|
|
481
|
+
|
|
482
|
+
const pending = this.#unsizedImages();
|
|
483
|
+
if (pending.length === 0) {
|
|
484
|
+
this.#handover();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
this.#warnUnsized();
|
|
488
|
+
|
|
489
|
+
let left = pending.length;
|
|
490
|
+
const done = () => {
|
|
491
|
+
if (--left > 0) return;
|
|
492
|
+
release();
|
|
493
|
+
this.#handover();
|
|
494
|
+
};
|
|
495
|
+
// Never wait indefinitely: a request that never answers must not leave the
|
|
496
|
+
// row static for the rest of the session. Past the cap the old behaviour
|
|
497
|
+
// takes over, and refresh() restates the phase when the image does land.
|
|
498
|
+
const timer = setTimeout(() => {
|
|
499
|
+
release();
|
|
500
|
+
this.#handover();
|
|
501
|
+
}, MEDIA_TIMEOUT);
|
|
502
|
+
const release = () => {
|
|
503
|
+
this.waiting = null;
|
|
504
|
+
clearTimeout(timer);
|
|
505
|
+
for (const img of pending) {
|
|
506
|
+
img.removeEventListener('load', done);
|
|
507
|
+
img.removeEventListener('error', done);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
this.waiting = release;
|
|
512
|
+
for (const img of pending) {
|
|
513
|
+
img.addEventListener('load', done);
|
|
514
|
+
img.addEventListener('error', done);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Overhang, measurement, clones and the first transform, in one task.
|
|
520
|
+
* Anything left over for the next frame gets painted on its own.
|
|
521
|
+
*/
|
|
522
|
+
#handover() {
|
|
523
|
+
if (this.destroyed) return;
|
|
401
524
|
this.refresh();
|
|
402
525
|
if (this.period > 0 && !this.paused) {
|
|
403
526
|
this.read(window.innerHeight);
|
|
@@ -405,6 +528,27 @@ class Marquee {
|
|
|
405
528
|
}
|
|
406
529
|
}
|
|
407
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Say something when an item holds no space until it loads.
|
|
533
|
+
*
|
|
534
|
+
* The row recovers on its own now, but it recovers by standing still until
|
|
535
|
+
* the last image lands, which on a slow connection is the row not starting
|
|
536
|
+
* for a second or two. The fix belongs in the markup, costs one attribute
|
|
537
|
+
* pair, and is worth knowing about: `width` and `height` on the `<img>` also
|
|
538
|
+
* spare the reader the layout shift the rest of the page pays for.
|
|
539
|
+
*
|
|
540
|
+
* Once per page, like the spacing warning.
|
|
541
|
+
*/
|
|
542
|
+
#warnUnsized() {
|
|
543
|
+
if (warnedUnsized) return;
|
|
544
|
+
warnedUnsized = true;
|
|
545
|
+
console.warn(
|
|
546
|
+
'wake-marquee: an item holds no width until it loads, so the loop period is not ' +
|
|
547
|
+
'measurable yet and the row is waiting. Give each <img> its width and height ' +
|
|
548
|
+
'attributes, or an aspect-ratio, and it starts immediately.',
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
408
552
|
#observe() {
|
|
409
553
|
const root = this.element;
|
|
410
554
|
|
|
@@ -520,10 +664,12 @@ class Marquee {
|
|
|
520
664
|
refresh() {
|
|
521
665
|
if (this.destroyed) return;
|
|
522
666
|
|
|
523
|
-
const period = this.lane
|
|
667
|
+
const period = layoutWidth(this.lane);
|
|
524
668
|
// A display:none ancestor, or a lane whose images have not laid out yet,
|
|
525
|
-
// measures
|
|
526
|
-
// 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.
|
|
527
673
|
if (!(period > 0)) return;
|
|
528
674
|
|
|
529
675
|
// The period is the unit the offset is expressed in, so when it changes
|
|
@@ -540,7 +686,19 @@ class Marquee {
|
|
|
540
686
|
this.measured = true;
|
|
541
687
|
this.#setOverhang();
|
|
542
688
|
|
|
543
|
-
|
|
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);
|
|
544
702
|
|
|
545
703
|
while (this.lanes.length < needed) {
|
|
546
704
|
const clone = /** @type {HTMLElement} */ (this.lane.cloneNode(true));
|
|
@@ -560,6 +718,11 @@ class Marquee {
|
|
|
560
718
|
// hole in the row. The source is identical to the original, so this is
|
|
561
719
|
// a cache hit rather than a second download.
|
|
562
720
|
clone.querySelectorAll('img[loading="lazy"]').forEach((img) => img.setAttribute('loading', 'eager'));
|
|
721
|
+
// A ResizeObserver is notified after the same frame's rAF callbacks, so
|
|
722
|
+
// a lane added from one would be painted once before the loop could
|
|
723
|
+
// transform it, sitting up to a whole period right of its siblings for
|
|
724
|
+
// that frame. Matching it to them before it is attached costs nothing.
|
|
725
|
+
clone.style.transform = this.lane.style.transform;
|
|
563
726
|
this.track.appendChild(clone);
|
|
564
727
|
this.lanes.push(clone);
|
|
565
728
|
}
|
|
@@ -608,17 +771,19 @@ class Marquee {
|
|
|
608
771
|
/** Read geometry. Never writes, so it cannot force a layout mid-frame. */
|
|
609
772
|
read(viewport) {
|
|
610
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.
|
|
611
779
|
const rect = this.element.getBoundingClientRect();
|
|
612
780
|
const progress = viewProgress(rect.top, rect.height, viewport);
|
|
613
|
-
// Kept because the first frame's alignment has to undo exactly the
|
|
614
|
-
// overhang the track was given, and that is measured in these same px.
|
|
615
|
-
this.amplitude = (this.options.wake * rect.width) / 100;
|
|
616
781
|
this.wakePx = wakeOffset(progress, this.amplitude, this.dirSign);
|
|
617
|
-
// Resolved here rather than in write()
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
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);
|
|
622
787
|
}
|
|
623
788
|
|
|
624
789
|
/**
|
|
@@ -695,6 +860,7 @@ class Marquee {
|
|
|
695
860
|
registry.delete(this);
|
|
696
861
|
byElement.delete(this.element);
|
|
697
862
|
|
|
863
|
+
this.waiting?.();
|
|
698
864
|
this.intersection?.disconnect();
|
|
699
865
|
this.resize?.disconnect();
|
|
700
866
|
if (this.onEnter) this.element.removeEventListener('pointerenter', this.onEnter);
|