wake-marquee 0.2.0 → 0.2.1
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 +26 -1
- package/README.md +14 -8
- package/dist/wake-marquee.auto.js +1 -1
- package/dist/wake-marquee.js +1 -1
- package/package.json +1 -1
- package/src/marquee.js +121 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,30 @@ 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.1] - 2026-09-01
|
|
12
|
+
|
|
13
|
+
Two ways the row could still rearrange itself in front of the reader
|
|
14
|
+
after it had already started.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- A row of images with no `width` and `height` no longer jerks sideways once
|
|
19
|
+
for every image that arrives. An unloaded, unsized `<img>` lays out zero
|
|
20
|
+
pixels wide, and the lane's width is the loop period, so the row was
|
|
21
|
+
starting on a period made of its gaps and nothing else: eight logos over a
|
|
22
|
+
phone connection measured 384px at the first frame and 1608px a second and a
|
|
23
|
+
half later, and yanked by up to 216px on each step in between. A row whose
|
|
24
|
+
items reserve no space now waits for them and hands over once, which is
|
|
25
|
+
invisible because what stands there meanwhile is the static row that was on
|
|
26
|
+
screen anyway. The wait is capped, so a request that never answers costs a
|
|
27
|
+
late start rather than a row that never runs. It is the safe behaviour and
|
|
28
|
+
not the good one, so it also warns, once per page: the fix is two attributes
|
|
29
|
+
in the markup, and they spare the rest of the page the same layout shift.
|
|
30
|
+
- A lane added by a resize is given the loop's current transform before it is
|
|
31
|
+
attached. `ResizeObserver` is notified after the same frame's
|
|
32
|
+
`requestAnimationFrame` callbacks, so a lane cloned from one was painted once
|
|
33
|
+
at its untransformed position, up to a whole period right of its siblings.
|
|
34
|
+
|
|
11
35
|
## [0.2.0] - 2026-09-01
|
|
12
36
|
|
|
13
37
|
Three things adopting `0.1.0` in a real project turned up.
|
|
@@ -110,6 +134,7 @@ First release.
|
|
|
110
134
|
- Right-to-left writing modes are not supported. Flex rows follow the writing
|
|
111
135
|
direction while the transforms are physical, so the two disagree.
|
|
112
136
|
|
|
113
|
-
[Unreleased]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.
|
|
137
|
+
[Unreleased]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.1...HEAD
|
|
138
|
+
[0.2.1]: https://github.com/robin-gogolok/wake-marquee/compare/v0.2.0...v0.2.1
|
|
114
139
|
[0.2.0]: https://github.com/robin-gogolok/wake-marquee/compare/v0.1.0...v0.2.0
|
|
115
140
|
[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
|
|
|
@@ -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 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);})();
|
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 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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wake-marquee",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
|
|
@@ -138,6 +145,9 @@ let lastFrame = 0;
|
|
|
138
145
|
/** Warned about collapsed spacing once? A global reset breaks every row. */
|
|
139
146
|
let warnedSpacing = false;
|
|
140
147
|
|
|
148
|
+
/** Warned about an image with no reserved space once? Same reasoning. */
|
|
149
|
+
let warnedUnsized = false;
|
|
150
|
+
|
|
141
151
|
/**
|
|
142
152
|
* Read the scroll offset of either the window or an overflow container.
|
|
143
153
|
* @param {Window | HTMLElement} scroller
|
|
@@ -292,6 +302,13 @@ class Marquee {
|
|
|
292
302
|
/** Has the first frame been lined up with the static row it replaces? */
|
|
293
303
|
this.aligned = false;
|
|
294
304
|
|
|
305
|
+
/**
|
|
306
|
+
* @type {(() => void) | null} Cancels the wait for an image that has not
|
|
307
|
+
* reserved its space yet, while there is one. Doubles as the guard that
|
|
308
|
+
* keeps a second observer callback from starting a second wait.
|
|
309
|
+
*/
|
|
310
|
+
this.waiting = null;
|
|
311
|
+
|
|
295
312
|
this.visible = false;
|
|
296
313
|
this.measured = false;
|
|
297
314
|
this.paused = false;
|
|
@@ -393,11 +410,85 @@ class Marquee {
|
|
|
393
410
|
}
|
|
394
411
|
|
|
395
412
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
413
|
+
* The images in the lane that will move the loop period when they arrive.
|
|
414
|
+
*
|
|
415
|
+
* An `<img>` with no width and height in the markup lays out zero pixels
|
|
416
|
+
* wide until it has decoded, and the lane's width *is* the period. Measure
|
|
417
|
+
* now and the row runs on a period that is a fraction of its real one, then
|
|
418
|
+
* grows into it a logo at a time: eight logos, eight sideways yanks, spread
|
|
419
|
+
* over however long the slowest of them takes. A row of eight unsized
|
|
420
|
+
* wordmarks measures 384px at the first frame and 1608px a second and a
|
|
421
|
+
* half later, and jerks by up to 216px on each step in between.
|
|
422
|
+
*
|
|
423
|
+
* Deliberately a measurement rather than a plain `!complete`. An image that
|
|
424
|
+
* already holds its own box cannot change the period whatever it turns out
|
|
425
|
+
* to contain, so markup that reserves its space needs no waiting at all and
|
|
426
|
+
* keeps the single synchronous handover it has today.
|
|
427
|
+
*
|
|
428
|
+
* @returns {HTMLImageElement[]}
|
|
429
|
+
*/
|
|
430
|
+
#unsizedImages() {
|
|
431
|
+
const pending = [];
|
|
432
|
+
for (const img of this.lane.querySelectorAll('img')) {
|
|
433
|
+
if (!img.complete && img.getBoundingClientRect().width === 0) pending.push(img);
|
|
434
|
+
}
|
|
435
|
+
return pending;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Take the row from its static state to its running one, once the period it
|
|
440
|
+
* would measure is the period it is going to keep.
|
|
441
|
+
*
|
|
442
|
+
* Held back while the lane still contains images that reserve no space. The
|
|
443
|
+
* wait costs nothing anybody can see: what stands there in the meantime is
|
|
444
|
+
* the static clipped row, which is what was on screen anyway, and it now
|
|
445
|
+
* becomes a moving row exactly once instead of stepping into place.
|
|
399
446
|
*/
|
|
400
447
|
#activate() {
|
|
448
|
+
if (this.waiting) return;
|
|
449
|
+
|
|
450
|
+
const pending = this.#unsizedImages();
|
|
451
|
+
if (pending.length === 0) {
|
|
452
|
+
this.#handover();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
this.#warnUnsized();
|
|
456
|
+
|
|
457
|
+
let left = pending.length;
|
|
458
|
+
const done = () => {
|
|
459
|
+
if (--left > 0) return;
|
|
460
|
+
release();
|
|
461
|
+
this.#handover();
|
|
462
|
+
};
|
|
463
|
+
// Never wait indefinitely: a request that never answers must not leave the
|
|
464
|
+
// row static for the rest of the session. Past the cap the old behaviour
|
|
465
|
+
// takes over, and refresh() restates the phase when the image does land.
|
|
466
|
+
const timer = setTimeout(() => {
|
|
467
|
+
release();
|
|
468
|
+
this.#handover();
|
|
469
|
+
}, MEDIA_TIMEOUT);
|
|
470
|
+
const release = () => {
|
|
471
|
+
this.waiting = null;
|
|
472
|
+
clearTimeout(timer);
|
|
473
|
+
for (const img of pending) {
|
|
474
|
+
img.removeEventListener('load', done);
|
|
475
|
+
img.removeEventListener('error', done);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
this.waiting = release;
|
|
480
|
+
for (const img of pending) {
|
|
481
|
+
img.addEventListener('load', done);
|
|
482
|
+
img.addEventListener('error', done);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Overhang, measurement, clones and the first transform, in one task.
|
|
488
|
+
* Anything left over for the next frame gets painted on its own.
|
|
489
|
+
*/
|
|
490
|
+
#handover() {
|
|
491
|
+
if (this.destroyed) return;
|
|
401
492
|
this.refresh();
|
|
402
493
|
if (this.period > 0 && !this.paused) {
|
|
403
494
|
this.read(window.innerHeight);
|
|
@@ -405,6 +496,27 @@ class Marquee {
|
|
|
405
496
|
}
|
|
406
497
|
}
|
|
407
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Say something when an item holds no space until it loads.
|
|
501
|
+
*
|
|
502
|
+
* The row recovers on its own now, but it recovers by standing still until
|
|
503
|
+
* the last image lands, which on a slow connection is the row not starting
|
|
504
|
+
* for a second or two. The fix belongs in the markup, costs one attribute
|
|
505
|
+
* pair, and is worth knowing about: `width` and `height` on the `<img>` also
|
|
506
|
+
* spare the reader the layout shift the rest of the page pays for.
|
|
507
|
+
*
|
|
508
|
+
* Once per page, like the spacing warning.
|
|
509
|
+
*/
|
|
510
|
+
#warnUnsized() {
|
|
511
|
+
if (warnedUnsized) return;
|
|
512
|
+
warnedUnsized = true;
|
|
513
|
+
console.warn(
|
|
514
|
+
'wake-marquee: an item holds no width until it loads, so the loop period is not ' +
|
|
515
|
+
'measurable yet and the row is waiting. Give each <img> its width and height ' +
|
|
516
|
+
'attributes, or an aspect-ratio, and it starts immediately.',
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
408
520
|
#observe() {
|
|
409
521
|
const root = this.element;
|
|
410
522
|
|
|
@@ -560,6 +672,11 @@ class Marquee {
|
|
|
560
672
|
// hole in the row. The source is identical to the original, so this is
|
|
561
673
|
// a cache hit rather than a second download.
|
|
562
674
|
clone.querySelectorAll('img[loading="lazy"]').forEach((img) => img.setAttribute('loading', 'eager'));
|
|
675
|
+
// A ResizeObserver is notified after the same frame's rAF callbacks, so
|
|
676
|
+
// a lane added from one would be painted once before the loop could
|
|
677
|
+
// transform it, sitting up to a whole period right of its siblings for
|
|
678
|
+
// that frame. Matching it to them before it is attached costs nothing.
|
|
679
|
+
clone.style.transform = this.lane.style.transform;
|
|
563
680
|
this.track.appendChild(clone);
|
|
564
681
|
this.lanes.push(clone);
|
|
565
682
|
}
|
|
@@ -695,6 +812,7 @@ class Marquee {
|
|
|
695
812
|
registry.delete(this);
|
|
696
813
|
byElement.delete(this.element);
|
|
697
814
|
|
|
815
|
+
this.waiting?.();
|
|
698
816
|
this.intersection?.disconnect();
|
|
699
817
|
this.resize?.disconnect();
|
|
700
818
|
if (this.onEnter) this.element.removeEventListener('pointerenter', this.onEnter);
|