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 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.1...HEAD
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
- ### Horizontal, and left-to-right
412
+ ### Left to right
396
413
 
397
- Vertical marquees are not supported. Neither is a right-to-left writing mode: 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.
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);})();
@@ -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.1",
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.getBoundingClientRect().width;
667
+ const period = layoutWidth(this.lane);
636
668
  // A display:none ancestor, or a lane whose images have not laid out yet,
637
- // measures zero. Bailing leaves `measured` false so the next observer
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
- const needed = laneCount(this.track.getBoundingClientRect().width, period, LANE_BUFFER);
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(), because a percentage speed is a
735
- // fraction of this same width and this is where the width is honest. It
736
- // is re-read every frame, so an option changed at runtime takes effect on
737
- // the next one, and a container resized under a running row is followed.
738
- this.speedPx = resolveSpeed(this.options.speed, rect.width);
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
  /**