@cntrl-site/sdk-nextjs 0.19.4 → 0.20.0
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/.idea/inspectionProfiles/Project_Default.xml +15 -0
- package/cntrl-site-sdk-nextjs-0.19.4.tgz +0 -0
- package/lib/components/items/ImageItem.js +28 -7
- package/lib/utils/Animator/Animator.js +15 -22
- package/lib/utils/effects/useImageFx.js +61 -0
- package/lib/utils/rangeMap.js +12 -0
- package/lib/utils/useElementRect.js +25 -0
- package/package.json +3 -2
- package/src/components/items/ImageItem.tsx +43 -10
- package/src/utils/Animator/Animator.ts +1 -14
- package/src/utils/effects/useImageFx.ts +72 -0
- package/src/utils/rangeMap.ts +13 -0
- package/src/utils/useElementRect.ts +23 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
|
2
|
+
<profile version="1.0">
|
|
3
|
+
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
|
+
<option name="myValues">
|
|
6
|
+
<value>
|
|
7
|
+
<list size="1">
|
|
8
|
+
<item index="0" class="java.lang.String" itemvalue="jsx" />
|
|
9
|
+
</list>
|
|
10
|
+
</value>
|
|
11
|
+
</option>
|
|
12
|
+
<option name="myCustomValuesEnabled" value="true" />
|
|
13
|
+
</inspection_tool>
|
|
14
|
+
</profile>
|
|
15
|
+
</component>
|
|
Binary file
|
|
@@ -15,8 +15,10 @@ const useItemAngle_1 = require("../useItemAngle");
|
|
|
15
15
|
const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
16
16
|
const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
|
|
17
17
|
const useRegisterResize_1 = require("../../common/useRegisterResize");
|
|
18
|
+
const useImageFx_1 = require("../../utils/effects/useImageFx");
|
|
19
|
+
const useElementRect_1 = require("../../utils/useElementRect");
|
|
18
20
|
const ImageItem = ({ item, sectionId, onResize }) => {
|
|
19
|
-
var _a, _b;
|
|
21
|
+
var _a, _b, _c, _d;
|
|
20
22
|
const id = (0, react_1.useId)();
|
|
21
23
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
22
24
|
const { radius, strokeWidth, opacity, strokeColor, blur } = (0, useFileItem_1.useFileItem)(item, sectionId);
|
|
@@ -24,15 +26,25 @@ const ImageItem = ({ item, sectionId, onResize }) => {
|
|
|
24
26
|
const borderColor = (0, react_1.useMemo)(() => color_1.CntrlColor.parse(strokeColor), [strokeColor]);
|
|
25
27
|
const [ref, setRef] = (0, react_1.useState)(null);
|
|
26
28
|
(0, useRegisterResize_1.useRegisterResize)(ref, onResize);
|
|
27
|
-
|
|
29
|
+
const { url, hasGLEffect, fragmentShader } = item.commonParams;
|
|
30
|
+
const fxCanvas = (0, react_1.useRef)(null);
|
|
31
|
+
(0, useImageFx_1.useImageFx)(fxCanvas.current, hasGLEffect !== null && hasGLEffect !== void 0 ? hasGLEffect : false, {
|
|
32
|
+
imageUrl: url,
|
|
33
|
+
fragmentShader
|
|
34
|
+
});
|
|
35
|
+
const rect = (0, useElementRect_1.useElementRect)(ref);
|
|
36
|
+
const rectWidth = Math.floor((_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0);
|
|
37
|
+
const rectHeight = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0);
|
|
38
|
+
const inlineStyles = {
|
|
39
|
+
borderRadius: `${radius * 100}vw`,
|
|
40
|
+
borderWidth: `${strokeWidth * 100}vw`,
|
|
41
|
+
borderColor: `${borderColor.toCss()}`
|
|
42
|
+
};
|
|
43
|
+
return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_c = item.link) === null || _c === void 0 ? void 0 : _c.url, target: (_d = item.link) === null || _d === void 0 ? void 0 : _d.target }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `image-wrapper-${item.id}`, ref: setRef, style: {
|
|
28
44
|
opacity: `${opacity}`,
|
|
29
45
|
transform: `rotate(${angle}deg)`,
|
|
30
46
|
filter: `blur(${blur * 100}vw)`
|
|
31
|
-
} }, { children: (0, jsx_runtime_1.jsx)("img", { className: `image image-${item.id}`, style: {
|
|
32
|
-
borderRadius: `${radius * 100}vw`,
|
|
33
|
-
borderWidth: `${strokeWidth * 100}vw`,
|
|
34
|
-
borderColor: `${borderColor.toCss()}`
|
|
35
|
-
}, src: item.commonParams.url }) })), (0, jsx_runtime_1.jsx)(style_1.default, Object.assign({ id: id }, { children: `
|
|
47
|
+
} }, { children: item.commonParams.hasGLEffect ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: "img-canvas", width: rectWidth, height: rectHeight })) : ((0, jsx_runtime_1.jsx)("img", { alt: "", className: `image image-${item.id}`, style: inlineStyles, src: item.commonParams.url })) })), (0, jsx_runtime_1.jsx)(style_1.default, Object.assign({ id: id }, { children: `
|
|
36
48
|
@supports not (color: oklch(42% 0.3 90 / 1)) {
|
|
37
49
|
.image-${item.id} {
|
|
38
50
|
border-color: ${borderColor.fmt('rgba')};
|
|
@@ -53,6 +65,15 @@ const ImageItem = ({ item, sectionId, onResize }) => {
|
|
|
53
65
|
pointer-events: none;
|
|
54
66
|
border-style: solid;
|
|
55
67
|
overflow: hidden;
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
}
|
|
70
|
+
.img-canvas {
|
|
71
|
+
border: solid;
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
border-width: 0;
|
|
76
|
+
box-sizing: border-box;
|
|
56
77
|
}
|
|
57
78
|
${(0, sdk_1.getLayoutStyles)(layouts, [item.state.hover], ([hoverParams]) => {
|
|
58
79
|
return (`
|
|
@@ -4,6 +4,7 @@ exports.Animator = void 0;
|
|
|
4
4
|
const sdk_1 = require("@cntrl-site/sdk");
|
|
5
5
|
const color_1 = require("@cntrl-site/color");
|
|
6
6
|
const binSearchInsertAt_1 = require("../binSearchInsertAt");
|
|
7
|
+
const rangeMap_1 = require("../rangeMap");
|
|
7
8
|
const compare = (lhs, rhs) => lhs.position - rhs.position;
|
|
8
9
|
const insertBin = (0, binSearchInsertAt_1.createInsert)(binSearchInsertAt_1.binSearchInsertAt, compare);
|
|
9
10
|
class Animator {
|
|
@@ -28,8 +29,8 @@ class Animator {
|
|
|
28
29
|
}
|
|
29
30
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
30
31
|
return {
|
|
31
|
-
width: rangeMap(pos, start.position, end.position, start.value.width, end.value.width, true),
|
|
32
|
-
height: rangeMap(pos, start.position, end.position, start.value.height, end.value.height, true)
|
|
32
|
+
width: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.width, end.value.width, true),
|
|
33
|
+
height: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.height, end.value.height, true)
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
getPositions(values, pos) {
|
|
@@ -45,8 +46,8 @@ class Animator {
|
|
|
45
46
|
}
|
|
46
47
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
47
48
|
return {
|
|
48
|
-
left: rangeMap(pos, start.position, end.position, start.value.left, end.value.left, true),
|
|
49
|
-
top: rangeMap(pos, start.position, end.position, start.value.top, end.value.top, true)
|
|
49
|
+
left: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.left, end.value.left, true),
|
|
50
|
+
top: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.top, end.value.top, true)
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
getColor(values, pos) {
|
|
@@ -91,7 +92,7 @@ class Animator {
|
|
|
91
92
|
}
|
|
92
93
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
93
94
|
return {
|
|
94
|
-
radius: rangeMap(pos, start.position, end.position, start.value.radius, end.value.radius, true)
|
|
95
|
+
radius: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.radius, end.value.radius, true)
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
getBorderWidth(values, pos) {
|
|
@@ -106,7 +107,7 @@ class Animator {
|
|
|
106
107
|
}
|
|
107
108
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
108
109
|
return {
|
|
109
|
-
borderWidth: rangeMap(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
|
|
110
|
+
borderWidth: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
getRotation(values, pos) {
|
|
@@ -121,7 +122,7 @@ class Animator {
|
|
|
121
122
|
}
|
|
122
123
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
123
124
|
return {
|
|
124
|
-
angle: rangeMap(pos, start.position, end.position, start.value.angle, end.value.angle, true)
|
|
125
|
+
angle: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.angle, end.value.angle, true)
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
128
|
getOpacity(values, pos) {
|
|
@@ -136,7 +137,7 @@ class Animator {
|
|
|
136
137
|
}
|
|
137
138
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
138
139
|
return {
|
|
139
|
-
opacity: rangeMap(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
|
|
140
|
+
opacity: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
getScale(values, pos) {
|
|
@@ -151,7 +152,7 @@ class Animator {
|
|
|
151
152
|
}
|
|
152
153
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
153
154
|
return {
|
|
154
|
-
scale: rangeMap(pos, start.position, end.position, start.value.scale, end.value.scale, true)
|
|
155
|
+
scale: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.scale, end.value.scale, true)
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
getBlur(values, pos) {
|
|
@@ -166,7 +167,7 @@ class Animator {
|
|
|
166
167
|
}
|
|
167
168
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
168
169
|
return {
|
|
169
|
-
blur: rangeMap(pos, start.position, end.position, start.value.blur, end.value.blur, true)
|
|
170
|
+
blur: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.blur, end.value.blur, true)
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
173
|
getBackdropBlur(values, pos) {
|
|
@@ -181,7 +182,7 @@ class Animator {
|
|
|
181
182
|
}
|
|
182
183
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
183
184
|
return {
|
|
184
|
-
backdropBlur: rangeMap(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
|
|
185
|
+
backdropBlur: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
getTextColor(values, pos) {
|
|
@@ -211,7 +212,7 @@ class Animator {
|
|
|
211
212
|
}
|
|
212
213
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
213
214
|
return {
|
|
214
|
-
letterSpacing: rangeMap(pos, start.position, end.position, start.value.letterSpacing, end.value.letterSpacing, true)
|
|
215
|
+
letterSpacing: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.letterSpacing, end.value.letterSpacing, true)
|
|
215
216
|
};
|
|
216
217
|
}
|
|
217
218
|
getWordSpacing(values, pos) {
|
|
@@ -226,7 +227,7 @@ class Animator {
|
|
|
226
227
|
}
|
|
227
228
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
228
229
|
return {
|
|
229
|
-
wordSpacing: rangeMap(pos, start.position, end.position, start.value.wordSpacing, end.value.wordSpacing, true)
|
|
230
|
+
wordSpacing: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.wordSpacing, end.value.wordSpacing, true)
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
getStartEnd(position, keyframes) {
|
|
@@ -242,7 +243,7 @@ class Animator {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
getRangeColor(start, end, position) {
|
|
245
|
-
const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
|
|
246
|
+
const rangeAmount = (0, rangeMap_1.rangeMap)(position, start.position, end.position, 0, 1, true);
|
|
246
247
|
const startColor = color_1.CntrlColor.parse(start.value.color);
|
|
247
248
|
const endColor = color_1.CntrlColor.parse(end.value.color);
|
|
248
249
|
const mixedColor = startColor.mix(endColor, rangeAmount);
|
|
@@ -268,11 +269,3 @@ function createKeyframesMap() {
|
|
|
268
269
|
[sdk_1.KeyframeType.TextColor]: []
|
|
269
270
|
};
|
|
270
271
|
}
|
|
271
|
-
function rangeMap(n, start1, stop1, start2, stop2, withinBounds = false) {
|
|
272
|
-
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
273
|
-
if (withinBounds && n < start1)
|
|
274
|
-
return start2;
|
|
275
|
-
if (withinBounds && n > stop1)
|
|
276
|
-
return stop2;
|
|
277
|
-
return mapped;
|
|
278
|
-
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useImageFx = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const effects_1 = require("@cntrl-site/effects");
|
|
6
|
+
const rangeMap_1 = require("../rangeMap");
|
|
7
|
+
function useImageFx(canvas, enabled, { imageUrl, fragmentShader }) {
|
|
8
|
+
const cursor = (0, react_1.useRef)([0, 0]);
|
|
9
|
+
const imageFx = (0, react_1.useMemo)(() => {
|
|
10
|
+
if (!imageUrl)
|
|
11
|
+
return undefined;
|
|
12
|
+
return new effects_1.ImageEffect(imageUrl, fragmentShader, { time: 0, cursor: cursor.current });
|
|
13
|
+
}, [imageUrl, fragmentShader]);
|
|
14
|
+
(0, react_1.useEffect)(() => {
|
|
15
|
+
if (!canvas || !imageFx)
|
|
16
|
+
return;
|
|
17
|
+
const handleMouseMove = (evt) => {
|
|
18
|
+
const rect = canvas.getBoundingClientRect();
|
|
19
|
+
const x = (0, rangeMap_1.rangeMap)(evt.clientX, rect.left, rect.left + rect.width, 0, 1);
|
|
20
|
+
const y = (0, rangeMap_1.rangeMap)(evt.clientY, rect.top, rect.top + rect.height, 0, 1);
|
|
21
|
+
imageFx.setParam('cursor', [x, y]);
|
|
22
|
+
};
|
|
23
|
+
window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
26
|
+
};
|
|
27
|
+
}, [canvas, imageFx]);
|
|
28
|
+
(0, react_1.useEffect)(() => {
|
|
29
|
+
const gl = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('webgl2');
|
|
30
|
+
if (!enabled || !canvas || !gl || !imageFx)
|
|
31
|
+
return;
|
|
32
|
+
let running = false;
|
|
33
|
+
let time = 0;
|
|
34
|
+
let frame;
|
|
35
|
+
const renderFrame = () => {
|
|
36
|
+
time += 0.1;
|
|
37
|
+
imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
|
|
38
|
+
imageFx.setParam('time', time);
|
|
39
|
+
imageFx.render(gl);
|
|
40
|
+
frame = requestAnimationFrame(renderFrame);
|
|
41
|
+
};
|
|
42
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
43
|
+
if (entry.isIntersecting && !running) {
|
|
44
|
+
frame = requestAnimationFrame(renderFrame);
|
|
45
|
+
running = true;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!entry.isIntersecting && running) {
|
|
49
|
+
window.cancelAnimationFrame(frame);
|
|
50
|
+
running = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
observer.observe(canvas);
|
|
54
|
+
imageFx.prepare(gl);
|
|
55
|
+
return () => {
|
|
56
|
+
window.cancelAnimationFrame(frame);
|
|
57
|
+
observer.disconnect();
|
|
58
|
+
};
|
|
59
|
+
}, [canvas, imageFx, enabled]);
|
|
60
|
+
}
|
|
61
|
+
exports.useImageFx = useImageFx;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rangeMap = void 0;
|
|
4
|
+
function rangeMap(n, start1, stop1, start2, stop2, withinBounds = false) {
|
|
5
|
+
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
6
|
+
if (withinBounds && n < start1)
|
|
7
|
+
return start2;
|
|
8
|
+
if (withinBounds && n > stop1)
|
|
9
|
+
return stop2;
|
|
10
|
+
return mapped;
|
|
11
|
+
}
|
|
12
|
+
exports.rangeMap = rangeMap;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useElementRect = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useElementRect(element) {
|
|
6
|
+
const [rect, setRect] = (0, react_1.useState)(() => element === null || element === void 0 ? void 0 : element.getBoundingClientRect());
|
|
7
|
+
(0, react_1.useEffect)(() => {
|
|
8
|
+
if (!element)
|
|
9
|
+
return;
|
|
10
|
+
const observer = new ResizeObserver(entries => {
|
|
11
|
+
const el = entries.find(entry => entry.target === element);
|
|
12
|
+
if (!el) {
|
|
13
|
+
throw new Error('Element is not found in observed entries');
|
|
14
|
+
}
|
|
15
|
+
setRect(el.target.getBoundingClientRect());
|
|
16
|
+
});
|
|
17
|
+
observer.observe(element);
|
|
18
|
+
return () => {
|
|
19
|
+
observer.unobserve(element);
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
};
|
|
22
|
+
}, [element]);
|
|
23
|
+
return rect;
|
|
24
|
+
}
|
|
25
|
+
exports.useElementRect = useElementRect;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cntrl-site/sdk-nextjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "SDK for Next.js",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"homepage": "https://github.com/cntrl-site/sdk-nextjs#readme",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@cntrl-site/color": "^1.0.0",
|
|
25
|
-
"@cntrl-site/
|
|
25
|
+
"@cntrl-site/effects": "^1.0.0",
|
|
26
|
+
"@cntrl-site/sdk": "^1.4.1",
|
|
26
27
|
"@types/vimeo__player": "^2.18.0",
|
|
27
28
|
"@vimeo/player": "^2.20.1",
|
|
28
29
|
"html-react-parser": "^3.0.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FC, useId, useMemo, useState } from 'react';
|
|
1
|
+
import { FC, useId, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import JSXStyle from 'styled-jsx/style';
|
|
3
3
|
import { CntrlColor } from '@cntrl-site/color';
|
|
4
4
|
import { ArticleItemType, getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
|
|
@@ -9,6 +9,8 @@ import { useItemAngle } from '../useItemAngle';
|
|
|
9
9
|
import { useCntrlContext } from '../../provider/useCntrlContext';
|
|
10
10
|
import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
|
|
11
11
|
import { useRegisterResize } from "../../common/useRegisterResize";
|
|
12
|
+
import { useImageFx } from '../../utils/effects/useImageFx';
|
|
13
|
+
import { useElementRect } from '../../utils/useElementRect';
|
|
12
14
|
|
|
13
15
|
export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize }) => {
|
|
14
16
|
const id = useId();
|
|
@@ -18,6 +20,20 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
18
20
|
const borderColor = useMemo(() => CntrlColor.parse(strokeColor), [strokeColor]);
|
|
19
21
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
20
22
|
useRegisterResize(ref, onResize);
|
|
23
|
+
const { url, hasGLEffect, fragmentShader } = item.commonParams;
|
|
24
|
+
const fxCanvas = useRef<HTMLCanvasElement | null>(null);
|
|
25
|
+
useImageFx(fxCanvas.current, hasGLEffect ?? false, {
|
|
26
|
+
imageUrl: url,
|
|
27
|
+
fragmentShader
|
|
28
|
+
});
|
|
29
|
+
const rect = useElementRect(ref);
|
|
30
|
+
const rectWidth = Math.floor(rect?.width ?? 0);
|
|
31
|
+
const rectHeight = Math.floor(rect?.height ?? 0);
|
|
32
|
+
const inlineStyles = {
|
|
33
|
+
borderRadius: `${radius * 100}vw`,
|
|
34
|
+
borderWidth: `${strokeWidth * 100}vw`,
|
|
35
|
+
borderColor: `${borderColor.toCss()}`
|
|
36
|
+
};
|
|
21
37
|
return (
|
|
22
38
|
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
23
39
|
<>
|
|
@@ -30,15 +46,23 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
30
46
|
filter: `blur(${blur * 100}vw)`
|
|
31
47
|
}}
|
|
32
48
|
>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
{item.commonParams.hasGLEffect ? (
|
|
50
|
+
<canvas
|
|
51
|
+
style={inlineStyles}
|
|
52
|
+
ref={fxCanvas}
|
|
53
|
+
className="img-canvas"
|
|
54
|
+
width={rectWidth}
|
|
55
|
+
height={rectHeight}
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<img
|
|
59
|
+
alt=""
|
|
60
|
+
className={`image image-${item.id}`}
|
|
61
|
+
style={inlineStyles}
|
|
62
|
+
src={item.commonParams.url}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
|
|
42
66
|
</div>
|
|
43
67
|
<JSXStyle id={id}>{`
|
|
44
68
|
@supports not (color: oklch(42% 0.3 90 / 1)) {
|
|
@@ -61,6 +85,15 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
61
85
|
pointer-events: none;
|
|
62
86
|
border-style: solid;
|
|
63
87
|
overflow: hidden;
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
}
|
|
90
|
+
.img-canvas {
|
|
91
|
+
border: solid;
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: 100%;
|
|
94
|
+
pointer-events: none;
|
|
95
|
+
border-width: 0;
|
|
96
|
+
box-sizing: border-box;
|
|
64
97
|
}
|
|
65
98
|
${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
|
|
66
99
|
return (`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KeyframeType, KeyframeValueMap } from '@cntrl-site/sdk';
|
|
2
2
|
import { CntrlColor } from '@cntrl-site/color';
|
|
3
3
|
import { binSearchInsertAt, createInsert } from '../binSearchInsertAt';
|
|
4
|
+
import { rangeMap } from '../rangeMap';
|
|
4
5
|
|
|
5
6
|
export interface AnimationData<T extends KeyframeType> {
|
|
6
7
|
position: number;
|
|
@@ -348,17 +349,3 @@ function createKeyframesMap(): KeyframesMap {
|
|
|
348
349
|
[KeyframeType.TextColor]: []
|
|
349
350
|
};
|
|
350
351
|
}
|
|
351
|
-
|
|
352
|
-
function rangeMap(
|
|
353
|
-
n: number,
|
|
354
|
-
start1: number,
|
|
355
|
-
stop1: number,
|
|
356
|
-
start2: number,
|
|
357
|
-
stop2: number,
|
|
358
|
-
withinBounds: boolean = false
|
|
359
|
-
): number {
|
|
360
|
-
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
361
|
-
if (withinBounds && n < start1) return start2;
|
|
362
|
-
if (withinBounds && n > stop1) return stop2;
|
|
363
|
-
return mapped;
|
|
364
|
-
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { ImageEffect } from '@cntrl-site/effects';
|
|
3
|
+
import { rangeMap } from '../rangeMap';
|
|
4
|
+
|
|
5
|
+
interface FxParams {
|
|
6
|
+
imageUrl?: string;
|
|
7
|
+
fragmentShader?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useImageFx(
|
|
11
|
+
canvas: HTMLCanvasElement | null | undefined,
|
|
12
|
+
enabled: boolean,
|
|
13
|
+
{
|
|
14
|
+
imageUrl,
|
|
15
|
+
fragmentShader
|
|
16
|
+
}: FxParams
|
|
17
|
+
): void {
|
|
18
|
+
const cursor = useRef<[number, number]>([0, 0]);
|
|
19
|
+
const imageFx = useMemo<ImageEffect | undefined>(() => {
|
|
20
|
+
if (!imageUrl) return undefined;
|
|
21
|
+
return new ImageEffect(imageUrl, fragmentShader!, { time: 0, cursor: cursor.current });
|
|
22
|
+
}, [imageUrl, fragmentShader]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!canvas || !imageFx) return;
|
|
26
|
+
const handleMouseMove = (evt: MouseEvent) => {
|
|
27
|
+
const rect = canvas.getBoundingClientRect();
|
|
28
|
+
const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1);
|
|
29
|
+
const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1);
|
|
30
|
+
imageFx.setParam('cursor', [x, y]);
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
|
|
33
|
+
return () => {
|
|
34
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
35
|
+
};
|
|
36
|
+
}, [canvas, imageFx]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const gl = canvas?.getContext('webgl2');
|
|
40
|
+
if (!enabled || !canvas || !gl || !imageFx) return;
|
|
41
|
+
let running = false;
|
|
42
|
+
let time = 0;
|
|
43
|
+
let frame: number;
|
|
44
|
+
|
|
45
|
+
const renderFrame = () => {
|
|
46
|
+
time += 0.1;
|
|
47
|
+
imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
|
|
48
|
+
imageFx.setParam('time', time);
|
|
49
|
+
imageFx.render(gl);
|
|
50
|
+
frame = requestAnimationFrame(renderFrame);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
54
|
+
if (entry.isIntersecting && !running) {
|
|
55
|
+
frame = requestAnimationFrame(renderFrame);
|
|
56
|
+
running = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!entry.isIntersecting && running) {
|
|
60
|
+
window.cancelAnimationFrame(frame);
|
|
61
|
+
running = false;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
observer.observe(canvas);
|
|
65
|
+
imageFx.prepare(gl);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
window.cancelAnimationFrame(frame);
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
};
|
|
71
|
+
}, [canvas, imageFx, enabled]);
|
|
72
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function rangeMap(
|
|
2
|
+
n: number,
|
|
3
|
+
start1: number,
|
|
4
|
+
stop1: number,
|
|
5
|
+
start2: number,
|
|
6
|
+
stop2: number,
|
|
7
|
+
withinBounds: boolean = false
|
|
8
|
+
): number {
|
|
9
|
+
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
10
|
+
if (withinBounds && n < start1) return start2;
|
|
11
|
+
if (withinBounds && n > stop1) return stop2;
|
|
12
|
+
return mapped;
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useElementRect(element: HTMLElement | null): DOMRect | undefined {
|
|
4
|
+
const [rect, setRect] = useState<DOMRect | undefined>(() => element?.getBoundingClientRect());
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!element) return;
|
|
8
|
+
const observer = new ResizeObserver(entries => {
|
|
9
|
+
const el = entries.find(entry => entry.target === element);
|
|
10
|
+
if (!el) {
|
|
11
|
+
throw new Error('Element is not found in observed entries');
|
|
12
|
+
}
|
|
13
|
+
setRect(el.target.getBoundingClientRect());
|
|
14
|
+
});
|
|
15
|
+
observer.observe(element);
|
|
16
|
+
return () => {
|
|
17
|
+
observer.unobserve(element);
|
|
18
|
+
observer.disconnect();
|
|
19
|
+
};
|
|
20
|
+
}, [element]);
|
|
21
|
+
|
|
22
|
+
return rect;
|
|
23
|
+
}
|