coonlink-luxy 26.1.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/README.md +36 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +143 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# coonlink-luxy
|
|
2
|
+
|
|
3
|
+
Inertia scroll and parallax (same idea as [luxy.js](https://github.com/min30327/luxy.js)), with fixes for modern browsers, `data-speed-y`, and clean `destroy()` for React/Next.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i coonlink-luxy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Markup
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<div id="luxy">
|
|
15
|
+
<div class="luxy-el" data-speed-y="5" data-offset="-50">…</div>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createCoonlinkLuxy } from 'coonlink-luxy'
|
|
23
|
+
|
|
24
|
+
const luxy = createCoonlinkLuxy()
|
|
25
|
+
luxy.init({
|
|
26
|
+
wrapper: '#luxy',
|
|
27
|
+
targets: '.luxy-el',
|
|
28
|
+
wrapperSpeed: 0.08,
|
|
29
|
+
targetSpeed: 0.02,
|
|
30
|
+
targetPercentage: 0.1,
|
|
31
|
+
respectReducedMotion: true,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// SPA / React unmount:
|
|
35
|
+
luxy.destroy()
|
|
36
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type CoonlinkLuxyOptions = {
|
|
2
|
+
wrapper?: string;
|
|
3
|
+
targets?: string;
|
|
4
|
+
wrapperSpeed?: number;
|
|
5
|
+
targetSpeed?: number;
|
|
6
|
+
targetPercentage?: number;
|
|
7
|
+
respectReducedMotion?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export interface CoonlinkLuxyInstance {
|
|
10
|
+
init(options?: CoonlinkLuxyOptions): boolean;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
}
|
|
13
|
+
declare const defaults: Required<CoonlinkLuxyOptions>;
|
|
14
|
+
export declare function createCoonlinkLuxy(): CoonlinkLuxyInstance;
|
|
15
|
+
export declare function getCoonlinkLuxyDefaults(): Readonly<typeof defaults>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAA;IAC5C,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,QAAA,MAAM,QAAQ,EAAE,QAAQ,CAAC,mBAAmB,CAO3C,CAAA;AAqKD,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD;AAED,wBAAgB,uBAAuB,IAAI,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAEnE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const defaults = {
|
|
2
|
+
wrapper: '#luxy',
|
|
3
|
+
targets: '.luxy-el',
|
|
4
|
+
wrapperSpeed: 0.08,
|
|
5
|
+
targetSpeed: 0.02,
|
|
6
|
+
targetPercentage: 0.1,
|
|
7
|
+
respectReducedMotion: true,
|
|
8
|
+
};
|
|
9
|
+
function extend(base, over) {
|
|
10
|
+
return { ...base, ...(over ?? {}) };
|
|
11
|
+
}
|
|
12
|
+
function getScrollTop() {
|
|
13
|
+
return (window.scrollY ??
|
|
14
|
+
document.documentElement.scrollTop ??
|
|
15
|
+
document.body.scrollTop ??
|
|
16
|
+
0);
|
|
17
|
+
}
|
|
18
|
+
function readSpeedY(el) {
|
|
19
|
+
return el.getAttribute('data-speed-y') ?? el.getAttribute('data-speed-Y');
|
|
20
|
+
}
|
|
21
|
+
class Engine {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.settings = defaults;
|
|
24
|
+
this.wrapper = null;
|
|
25
|
+
this.targets = [];
|
|
26
|
+
this.wrapperOffset = 0;
|
|
27
|
+
this.scrollTop = 0;
|
|
28
|
+
this.scrollRaf = 0;
|
|
29
|
+
this.resizeObserver = null;
|
|
30
|
+
this.onResize = () => {
|
|
31
|
+
if (!this.wrapper)
|
|
32
|
+
return;
|
|
33
|
+
const h = Math.max(this.wrapper.scrollHeight, this.wrapper.clientHeight);
|
|
34
|
+
document.body.style.height = `${h}px`;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
init(options) {
|
|
38
|
+
this.destroy();
|
|
39
|
+
this.settings = extend(defaults, options);
|
|
40
|
+
if (this.settings.respectReducedMotion) {
|
|
41
|
+
const mq = window.matchMedia?.('(prefers-reduced-motion: reduce)');
|
|
42
|
+
if (mq?.matches)
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const el = document.querySelector(this.settings.wrapper);
|
|
46
|
+
if (!el || !(el instanceof HTMLElement)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
this.wrapper = el;
|
|
50
|
+
const nodeList = document.querySelectorAll(this.settings.targets);
|
|
51
|
+
const scrollHeight = Math.max(this.wrapper.scrollHeight, this.wrapper.clientHeight);
|
|
52
|
+
document.body.style.height = `${scrollHeight}px`;
|
|
53
|
+
this.wrapper.style.width = '100%';
|
|
54
|
+
this.wrapper.style.position = 'fixed';
|
|
55
|
+
this.targets = [];
|
|
56
|
+
for (let i = 0; i < nodeList.length; i++) {
|
|
57
|
+
const node = nodeList[i];
|
|
58
|
+
if (!(node instanceof HTMLElement))
|
|
59
|
+
continue;
|
|
60
|
+
const offset = node.getAttribute('data-offset');
|
|
61
|
+
const speedX = node.getAttribute('data-speed-x');
|
|
62
|
+
const speedY = readSpeedY(node);
|
|
63
|
+
const percentage = node.getAttribute('data-percentage');
|
|
64
|
+
const horizontal = node.getAttribute('data-horizontal');
|
|
65
|
+
this.targets.push({
|
|
66
|
+
elm: node,
|
|
67
|
+
offset: offset ? parseInt(offset, 10) || 0 : 0,
|
|
68
|
+
horizontal: horizontal ? 1 : 0,
|
|
69
|
+
top: 0,
|
|
70
|
+
left: 0,
|
|
71
|
+
speedX: speedX ? Number(speedX) || 1 : 1,
|
|
72
|
+
speedY: speedY ? Number(speedY) || 1 : 1,
|
|
73
|
+
percentage: percentage ? parseInt(percentage, 10) || 0 : 0,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
this.resizeObserver = new ResizeObserver(this.onResize);
|
|
77
|
+
this.resizeObserver.observe(this.wrapper);
|
|
78
|
+
window.addEventListener('resize', this.onResize);
|
|
79
|
+
const tickScroll = () => {
|
|
80
|
+
this.scrollTop = getScrollTop();
|
|
81
|
+
this.wrapperUpdate();
|
|
82
|
+
for (let i = 0; i < this.targets.length; i++) {
|
|
83
|
+
this.targetsUpdate(this.targets[i]);
|
|
84
|
+
}
|
|
85
|
+
this.scrollRaf = window.requestAnimationFrame(tickScroll);
|
|
86
|
+
};
|
|
87
|
+
this.scrollRaf = window.requestAnimationFrame(tickScroll);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
wrapperUpdate() {
|
|
91
|
+
if (!this.wrapper)
|
|
92
|
+
return;
|
|
93
|
+
this.wrapperOffset +=
|
|
94
|
+
(this.scrollTop - this.wrapperOffset) * this.settings.wrapperSpeed;
|
|
95
|
+
const y = Math.round(-this.wrapperOffset * 100) / 100;
|
|
96
|
+
this.wrapper.style.transform = `translate3d(0, ${y}px, 0)`;
|
|
97
|
+
}
|
|
98
|
+
targetsUpdate(target) {
|
|
99
|
+
const ts = this.settings.targetSpeed;
|
|
100
|
+
const tp = this.settings.targetPercentage;
|
|
101
|
+
target.top +=
|
|
102
|
+
(this.scrollTop * Number(ts) * Number(target.speedY) - target.top) * tp;
|
|
103
|
+
target.left +=
|
|
104
|
+
(this.scrollTop * Number(ts) * Number(target.speedX) - target.left) * tp;
|
|
105
|
+
const targetOffsetTop = parseInt(String(target.percentage), 10) -
|
|
106
|
+
target.top -
|
|
107
|
+
parseInt(String(target.offset), 10);
|
|
108
|
+
let offsetY = Math.round(targetOffsetTop * -100) / 100;
|
|
109
|
+
let offsetX = 0;
|
|
110
|
+
if (target.horizontal) {
|
|
111
|
+
const targetOffsetLeft = parseInt(String(target.percentage), 10) -
|
|
112
|
+
target.left -
|
|
113
|
+
parseInt(String(target.offset), 10);
|
|
114
|
+
offsetX = Math.round(targetOffsetLeft * -100) / 100;
|
|
115
|
+
}
|
|
116
|
+
target.elm.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0)`;
|
|
117
|
+
}
|
|
118
|
+
destroy() {
|
|
119
|
+
window.removeEventListener('resize', this.onResize);
|
|
120
|
+
this.resizeObserver?.disconnect();
|
|
121
|
+
this.resizeObserver = null;
|
|
122
|
+
if (this.scrollRaf) {
|
|
123
|
+
window.cancelAnimationFrame(this.scrollRaf);
|
|
124
|
+
this.scrollRaf = 0;
|
|
125
|
+
}
|
|
126
|
+
document.body.style.height = '';
|
|
127
|
+
if (this.wrapper) {
|
|
128
|
+
this.wrapper.removeAttribute('style');
|
|
129
|
+
this.wrapper = null;
|
|
130
|
+
}
|
|
131
|
+
for (let i = 0; i < this.targets.length; i++) {
|
|
132
|
+
this.targets[i].elm.removeAttribute('style');
|
|
133
|
+
}
|
|
134
|
+
this.targets = [];
|
|
135
|
+
this.wrapperOffset = 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function createCoonlinkLuxy() {
|
|
139
|
+
return new Engine();
|
|
140
|
+
}
|
|
141
|
+
export function getCoonlinkLuxyDefaults() {
|
|
142
|
+
return { ...defaults };
|
|
143
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coonlink-luxy",
|
|
3
|
+
"version": "26.1.1",
|
|
4
|
+
"description": "Inertia scroll and parallax (luxy-style) with fixes for modern browsers, React/Next, and prefers-reduced-motion.",
|
|
5
|
+
"homepage": "https://dev.coonlink.com",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"parallax",
|
|
8
|
+
"scroll",
|
|
9
|
+
"inertia",
|
|
10
|
+
"smooth",
|
|
11
|
+
"luxy"
|
|
12
|
+
],
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.json",
|
|
28
|
+
"prepare": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "~6.0.2"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
}
|
|
36
|
+
}
|