ngx-astro-flickering-grid 1.0.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/package.json +30 -0
- package/src/FlickeringGrid.astro +214 -0
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ngx-astro-flickering-grid",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Flickering grid effect for Astro. Port of @omnedia/ngx-flickering-grid without Angular runtime.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/FlickeringGrid.astro",
|
|
9
|
+
"./FlickeringGrid.astro": "./src/FlickeringGrid.astro"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"astro": ">=5.0.0"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"astro",
|
|
19
|
+
"astro-component",
|
|
20
|
+
"grid",
|
|
21
|
+
"flickering",
|
|
22
|
+
"effect",
|
|
23
|
+
"animation",
|
|
24
|
+
"canvas"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/whitedotjsx/ngx-ui-to-astro.git"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
squareSize?: number;
|
|
4
|
+
gridGap?: number;
|
|
5
|
+
flickerChance?: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
maxOpacity?: number;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
squareSize = 4,
|
|
13
|
+
gridGap = 6,
|
|
14
|
+
flickerChance = 0.3,
|
|
15
|
+
color = '#6B7280',
|
|
16
|
+
maxOpacity = 0.3,
|
|
17
|
+
class: className = '',
|
|
18
|
+
} = Astro.props;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class:list={['om-flickering-grid', className]}
|
|
23
|
+
data-om-flickering-grid
|
|
24
|
+
data-square-size={squareSize}
|
|
25
|
+
data-grid-gap={gridGap}
|
|
26
|
+
data-flicker-chance={flickerChance}
|
|
27
|
+
data-color={color}
|
|
28
|
+
data-max-opacity={maxOpacity}
|
|
29
|
+
>
|
|
30
|
+
<div class="om-flickering-grid-background" data-om-fg-bg>
|
|
31
|
+
<canvas class="om-flickering-grid-canvas" data-om-fg-canvas width="100" height="100"></canvas>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="om-flickering-grid-content">
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.om-flickering-grid {
|
|
40
|
+
position: relative;
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
|
+
|
|
44
|
+
.om-flickering-grid-background {
|
|
45
|
+
position: absolute;
|
|
46
|
+
width: 100%;
|
|
47
|
+
height: 100%;
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
|
|
51
|
+
canvas {
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
z-index: 0;
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.om-flickering-grid-content {
|
|
60
|
+
position: relative;
|
|
61
|
+
z-index: 1;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
|
|
68
|
+
<script>
|
|
69
|
+
(() => {
|
|
70
|
+
const wrappers = document.querySelectorAll<HTMLElement>('[data-om-flickering-grid]');
|
|
71
|
+
if (!wrappers.length) return;
|
|
72
|
+
|
|
73
|
+
wrappers.forEach((wrapper) => {
|
|
74
|
+
const background = wrapper.querySelector<HTMLElement>('[data-om-fg-bg]')!;
|
|
75
|
+
const canvas = wrapper.querySelector<HTMLCanvasElement>('[data-om-fg-canvas]')!;
|
|
76
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true })!;
|
|
77
|
+
|
|
78
|
+
const config = {
|
|
79
|
+
squareSize: Number(wrapper.dataset.squareSize) || 4,
|
|
80
|
+
gridGap: Number(wrapper.dataset.gridGap) || 6,
|
|
81
|
+
flickerChance: Number(wrapper.dataset.flickerChance) || 0.3,
|
|
82
|
+
color: wrapper.dataset.color || '#6B7280',
|
|
83
|
+
maxOpacity: Number(wrapper.dataset.maxOpacity) || 0.3,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let cols = 0;
|
|
87
|
+
let rows = 0;
|
|
88
|
+
let squares: Float32Array = new Float32Array(0);
|
|
89
|
+
let lastAnimationTime = 0;
|
|
90
|
+
let memoizedColor = 'rgba(0, 0, 0,';
|
|
91
|
+
let isInView = false;
|
|
92
|
+
let animating = false;
|
|
93
|
+
|
|
94
|
+
function setMemoizedColor(): void {
|
|
95
|
+
const probe = document.createElement('canvas');
|
|
96
|
+
probe.width = probe.height = 1;
|
|
97
|
+
const pctx = probe.getContext('2d', { willReadFrequently: true });
|
|
98
|
+
if (!pctx) return;
|
|
99
|
+
pctx.fillStyle = config.color;
|
|
100
|
+
pctx.fillRect(0, 0, 1, 1);
|
|
101
|
+
const [r, g, b] = pctx.getImageData(0, 0, 1, 1).data;
|
|
102
|
+
memoizedColor = `rgba(${r}, ${g}, ${b},`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function setupCanvas(): void {
|
|
106
|
+
cols = Math.ceil(canvas.width / (config.squareSize + config.gridGap));
|
|
107
|
+
rows = Math.ceil(canvas.height / (config.squareSize + config.gridGap));
|
|
108
|
+
squares = new Float32Array(cols * rows);
|
|
109
|
+
for (let i = 0; i < squares.length; i++) {
|
|
110
|
+
squares[i] = Math.random() * config.maxOpacity;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setCanvasSize(): void {
|
|
115
|
+
canvas.width = background.getBoundingClientRect().width;
|
|
116
|
+
canvas.height = background.getBoundingClientRect().height;
|
|
117
|
+
setupCanvas();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function updateSquares(deltaTime: number): void {
|
|
121
|
+
for (let i = 0; i < squares.length; i++) {
|
|
122
|
+
if (Math.random() < config.flickerChance * deltaTime) {
|
|
123
|
+
squares[i] = Math.random() * config.maxOpacity;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function drawGrid(): void {
|
|
129
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
130
|
+
ctx.fillStyle = 'transparent';
|
|
131
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < cols; i++) {
|
|
134
|
+
for (let j = 0; j < rows; j++) {
|
|
135
|
+
const opacity = squares[i * rows + j];
|
|
136
|
+
ctx.fillStyle = `${memoizedColor}${opacity})`;
|
|
137
|
+
ctx.fillRect(
|
|
138
|
+
i * (config.squareSize + config.gridGap),
|
|
139
|
+
j * (config.squareSize + config.gridGap),
|
|
140
|
+
config.squareSize,
|
|
141
|
+
config.squareSize,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function animateCanvas(time: number): void {
|
|
148
|
+
if (!isInView) {
|
|
149
|
+
animating = false;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
animating = true;
|
|
153
|
+
const deltaTime = (time - lastAnimationTime) / 1000;
|
|
154
|
+
lastAnimationTime = time;
|
|
155
|
+
updateSquares(deltaTime);
|
|
156
|
+
drawGrid();
|
|
157
|
+
requestAnimationFrame(animateCanvas);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const onResize = () => setCanvasSize();
|
|
161
|
+
|
|
162
|
+
setCanvasSize();
|
|
163
|
+
setMemoizedColor();
|
|
164
|
+
window.addEventListener('resize', onResize);
|
|
165
|
+
|
|
166
|
+
if (!animating) {
|
|
167
|
+
requestAnimationFrame(animateCanvas);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
171
|
+
if (entry.isIntersecting && !isInView) {
|
|
172
|
+
isInView = true;
|
|
173
|
+
if (!animating) {
|
|
174
|
+
requestAnimationFrame(animateCanvas);
|
|
175
|
+
}
|
|
176
|
+
} else if (!entry.isIntersecting) {
|
|
177
|
+
isInView = false;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
observer.observe(canvas);
|
|
181
|
+
|
|
182
|
+
const attrObserver = new MutationObserver((muts) => {
|
|
183
|
+
let size = false;
|
|
184
|
+
let colorChanged = false;
|
|
185
|
+
for (const m of muts) {
|
|
186
|
+
if (m.attributeName === 'data-square-size' || m.attributeName === 'data-grid-gap') {
|
|
187
|
+
size = true;
|
|
188
|
+
} else if (m.attributeName === 'data-color') {
|
|
189
|
+
colorChanged = true;
|
|
190
|
+
} else if (m.attributeName === 'data-max-opacity' || m.attributeName === 'data-flicker-chance') {
|
|
191
|
+
size = true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
config.squareSize = Number(wrapper.dataset.squareSize) || 4;
|
|
195
|
+
config.gridGap = Number(wrapper.dataset.gridGap) || 6;
|
|
196
|
+
config.flickerChance = Number(wrapper.dataset.flickerChance) || 0.3;
|
|
197
|
+
config.color = wrapper.dataset.color || '#6B7280';
|
|
198
|
+
config.maxOpacity = Number(wrapper.dataset.maxOpacity) || 0.3;
|
|
199
|
+
if (size) setCanvasSize();
|
|
200
|
+
if (colorChanged) setMemoizedColor();
|
|
201
|
+
});
|
|
202
|
+
attrObserver.observe(wrapper, {
|
|
203
|
+
attributes: true,
|
|
204
|
+
attributeFilter: [
|
|
205
|
+
'data-color',
|
|
206
|
+
'data-max-opacity',
|
|
207
|
+
'data-square-size',
|
|
208
|
+
'data-grid-gap',
|
|
209
|
+
'data-flicker-chance',
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
})();
|
|
214
|
+
</script>
|