1
- const heroAnimation = async ( ) => {
1
+ const heroAnimation = async ( animContainer ) => {
2
2
const isReduceMotionEnabled = window . matchMedia (
3
3
'(prefers-reduced-motion: reduce)' ,
4
4
) . matches
@@ -14,6 +14,10 @@ const heroAnimation = async () => {
14
14
} )
15
15
}
16
16
17
+ // Skip to visible portion of animation when cropped on small screens
18
+ const { left, width } = animContainer . getClientRects ( ) [ 0 ]
19
+ const offScreenDelta = Math . abs ( left ) / width
20
+
17
21
const heroSwoops = [
18
22
{
19
23
canvas : document . querySelector ( '#purple-swoop' ) ,
@@ -25,7 +29,7 @@ const heroAnimation = async () => {
25
29
lineWidth : 210 ,
26
30
debugColor : 'purple' ,
27
31
image : null ,
28
- state : { progress : 0 } ,
32
+ state : { progress : offScreenDelta } ,
29
33
} ,
30
34
{
31
35
canvas : document . querySelector ( '#white-swoop-1' ) ,
@@ -37,7 +41,7 @@ const heroAnimation = async () => {
37
41
lineWidth : 140 ,
38
42
debugColor : 'red' ,
39
43
image : null ,
40
- state : { progress : 0 } ,
44
+ state : { progress : offScreenDelta } ,
41
45
} ,
42
46
{
43
47
canvas : document . querySelector ( '#white-swoop-2' ) ,
@@ -49,7 +53,7 @@ const heroAnimation = async () => {
49
53
lineWidth : 73.6 ,
50
54
debugColor : 'cyan' ,
51
55
image : null ,
52
- state : { progress : 0 } ,
56
+ state : { progress : offScreenDelta } ,
53
57
} ,
54
58
{
55
59
canvas : document . querySelector ( '#orange-swoop-bottom' ) ,
@@ -61,7 +65,7 @@ const heroAnimation = async () => {
61
65
lineWidth : 202.2 ,
62
66
debugColor : 'yellow' ,
63
67
image : null ,
64
- state : { progress : 0 } ,
68
+ state : { progress : offScreenDelta } ,
65
69
} ,
66
70
{
67
71
canvas : document . querySelector ( '#orange-swoop-top' ) ,
@@ -73,7 +77,7 @@ const heroAnimation = async () => {
73
77
lineWidth : 163.4 ,
74
78
debugColor : 'green' ,
75
79
image : null ,
76
- state : { progress : 0 } ,
80
+ state : { progress : offScreenDelta } ,
77
81
} ,
78
82
]
79
83
const logo = {
@@ -84,8 +88,9 @@ const heroAnimation = async () => {
84
88
position : [ 610 , 672.5 ] ,
85
89
imagePath : '/assets/images/landing-page/hero/bird.png' ,
86
90
image : null ,
87
- state : { progress : 0 } ,
91
+ state : { progress : offScreenDelta } ,
88
92
}
93
+
89
94
const initSwoops = ( {
90
95
path,
91
96
pathLength,
@@ -96,13 +101,12 @@ const heroAnimation = async () => {
96
101
image,
97
102
} ) => {
98
103
const ctx = canvas . getContext ( '2d' )
99
- // Convert position value to account for the center anchor point in AE
104
+ // The reference animation's transform origin is in the center of the canvas
100
105
// We're not going to reset this as it will make pulling values directly from AE easier
101
106
ctx . translate ( posX - image . naturalWidth / 2 , posY - image . naturalHeight / 2 )
102
107
// Set mask styles
103
108
ctx . lineWidth = lineWidth
104
109
ctx . lineCap = 'round'
105
- // Convert SVG path pulled from AE masks
106
110
let pathInstance = new Path2D ( path )
107
111
108
112
if ( ! isReduceMotionEnabled ) {
@@ -127,13 +131,10 @@ const heroAnimation = async () => {
127
131
positionEnd : [ endX , endY ] ,
128
132
} ) => {
129
133
const ctx = canvas . getContext ( '2d' )
130
- // Same reason for conversion as initSwoops
134
+ // Applying this conversion for the same purpose as init swoops
131
135
ctx . translate ( posX - image . naturalWidth / 2 , posY - image . naturalHeight / 2 )
132
136
133
- if ( ! isReduceMotionEnabled ) {
134
- ctx . globalAlpha = 0
135
- ctx . drawImage ( image , 0 , 0 )
136
- } else {
137
+ if ( isReduceMotionEnabled ) {
137
138
ctx . globalAlpha = 1
138
139
const deltaX = endX - posX
139
140
const deltaY = endY - posY
@@ -144,144 +145,119 @@ const heroAnimation = async () => {
144
145
}
145
146
146
147
try {
147
- // load swoop image
148
+ // Load swoop images
148
149
const swoopImages = await Promise . all (
149
150
heroSwoops . map ( ( swoop ) => loadImage ( swoop . imagePath ) ) ,
150
151
)
151
- // load logo
152
+ // Load logo
152
153
const logoImage = await loadImage ( logo . imagePath )
153
154
154
155
logo . image = logoImage
155
- // init canvas for each swoop layer
156
+ // Init canvas for each swoop layer
156
157
heroSwoops . forEach ( ( swoop , i ) => {
157
158
swoop . image = swoopImages [ i ]
158
159
const canvasData = initSwoops ( swoop )
159
160
swoop . ctx = canvasData . ctx
160
161
swoop . pathInstance = canvasData . pathInstance
161
162
} )
162
- // init logo canvas
163
+ // Init logo canvas
163
164
logo . ctx = initLogo ( logo )
164
165
} catch ( error ) {
165
166
console . error ( 'Error loading images:' , error )
166
167
throw error
167
168
}
168
169
170
+ // Skip animation if reduced motion is enabled
169
171
if ( isReduceMotionEnabled ) {
170
172
return
171
173
}
172
174
173
- const DURATION = 1000
175
+ const DURATION = 1000 - 1000 * offScreenDelta
174
176
175
177
const tl = anime . createTimeline ( {
176
178
defaults : { duration : DURATION , ease : 'inOut(.8)' } ,
177
179
} )
178
180
179
181
tl . label ( 'start' , 0 )
180
182
181
- // white swoop 1
183
+ const swoopUpdate = ( {
184
+ state,
185
+ ctx,
186
+ pathLength,
187
+ pathInstance,
188
+ image,
189
+ canvas,
190
+ } ) => {
191
+ // Clear canvas before next draw
192
+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
193
+ // Progress line dash offset
194
+ ctx . lineDashOffset = pathLength * ( 1 - state . progress )
195
+ // Draw stroke
196
+ ctx . stroke ( pathInstance )
197
+ // Source-in will allow us to only draw as far as the stroke
198
+ ctx . globalCompositeOperation = 'source-in'
199
+ ctx . drawImage ( image , 0 , 0 )
200
+ // Reset to default for our next stroke paint
201
+ ctx . globalCompositeOperation = 'source-out'
202
+ }
203
+
204
+ // White swoop 1
182
205
tl . add (
183
206
heroSwoops [ 1 ] . state ,
184
207
{
185
208
progress : 1 ,
186
- duration : 950 ,
187
- onUpdate : ( ) => {
188
- const { state, ctx, pathLength, pathInstance, image, canvas } =
189
- heroSwoops [ 1 ]
190
- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
191
- ctx . lineDashOffset = pathLength * ( 1 - state . progress )
192
- ctx . stroke ( pathInstance )
193
- ctx . globalCompositeOperation = 'source-in'
194
- ctx . drawImage ( image , 0 , 0 )
195
- ctx . globalCompositeOperation = 'source-out'
196
- } ,
209
+ duration : 950 - 950 * offScreenDelta ,
210
+ onUpdate : ( ) => swoopUpdate ( heroSwoops [ 1 ] ) ,
197
211
} ,
198
212
'start' ,
199
213
)
200
- // // purple swoop
214
+ // Purple swoop
201
215
tl . add (
202
216
heroSwoops [ 0 ] . state ,
203
217
{
204
218
progress : 1 ,
205
- duration : 950 ,
206
- onUpdate : ( ) => {
207
- const { state, ctx, pathLength, pathInstance, image, canvas } =
208
- heroSwoops [ 0 ]
209
- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
210
- ctx . lineDashOffset = pathLength * ( 1 - state . progress )
211
- ctx . stroke ( pathInstance )
212
- ctx . globalCompositeOperation = 'source-in'
213
- ctx . drawImage ( image , 0 , 0 )
214
- ctx . globalCompositeOperation = 'source-out'
215
- } ,
219
+ duration : 950 - 950 * offScreenDelta ,
220
+ onUpdate : ( ) => swoopUpdate ( heroSwoops [ 0 ] ) ,
216
221
} ,
217
222
'start' ,
218
223
)
219
- // // white swoop 2 swoop
224
+ // White swoop 2
220
225
tl . add (
221
226
heroSwoops [ 2 ] . state ,
222
227
{
223
228
progress : 1 ,
224
- onUpdate : ( ) => {
225
- const { state, ctx, pathLength, pathInstance, image, canvas } =
226
- heroSwoops [ 2 ]
227
- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
228
- ctx . lineDashOffset = pathLength * ( 1 - state . progress )
229
- ctx . stroke ( pathInstance )
230
- ctx . globalCompositeOperation = 'source-in'
231
- ctx . drawImage ( image , 0 , 0 )
232
- ctx . globalCompositeOperation = 'source-out'
233
- } ,
229
+ onUpdate : ( ) => swoopUpdate ( heroSwoops [ 2 ] ) ,
234
230
} ,
235
231
'start' ,
236
232
)
237
- // // orange swoop bottom
233
+ // Orange swoop bottom
238
234
tl . add (
239
235
heroSwoops [ 3 ] . state ,
240
236
{
241
237
progress : 1 ,
242
- onUpdate : ( ) => {
243
- const { state, ctx, pathLength, pathInstance, image, canvas } =
244
- heroSwoops [ 3 ]
245
- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
246
- ctx . lineDashOffset = pathLength * ( 1 - state . progress )
247
- ctx . stroke ( pathInstance )
248
- ctx . globalCompositeOperation = 'source-in'
249
- ctx . drawImage ( image , 0 , 0 )
250
- ctx . globalCompositeOperation = 'source-out'
251
- } ,
238
+ onUpdate : ( ) => swoopUpdate ( heroSwoops [ 3 ] ) ,
252
239
} ,
253
240
'start' ,
254
241
)
255
- // orange top
242
+ // Orange top
256
243
tl . add (
257
244
heroSwoops [ 4 ] . state ,
258
245
{
259
246
progress : 1 ,
260
- // ease: 'inOutQuad',
261
- duration : 480 ,
262
- delay : 520 ,
263
- onUpdate : ( ) => {
264
- const { state, ctx, pathLength, pathInstance, image, canvas } =
265
- heroSwoops [ 4 ]
266
- ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
267
- ctx . lineDashOffset = pathLength * ( 1 - state . progress )
268
- ctx . stroke ( pathInstance )
269
- ctx . globalCompositeOperation = 'source-in'
270
- ctx . drawImage ( image , 0 , 0 )
271
- ctx . globalCompositeOperation = 'source-out'
272
- } ,
247
+ duration : 480 - 480 * offScreenDelta ,
248
+ delay : 520 - 520 * offScreenDelta ,
249
+ onUpdate : ( ) => swoopUpdate ( heroSwoops [ 4 ] ) ,
273
250
} ,
274
251
'start' ,
275
252
)
276
- // logo
253
+ // Logo
277
254
tl . add (
278
255
logo . state ,
279
256
{
280
257
ease : 'out(1.1)' ,
281
- duration : 200 ,
282
- delay : 750 ,
258
+ duration : 200 - 200 * offScreenDelta ,
259
+ delay : 750 - 750 * offScreenDelta ,
283
260
progress : 1 ,
284
- // ease: 'inOutQuad',
285
261
onUpdate : ( ) => {
286
262
const {
287
263
state : { progress } ,
@@ -292,6 +268,7 @@ const heroAnimation = async () => {
292
268
positionEnd : [ endX , endY ] ,
293
269
} = logo
294
270
ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
271
+ // Progresses logo opacity from 0 to 1
295
272
ctx . globalAlpha = progress
296
273
const deltaX = ( endX - startX ) * progress
297
274
const deltaY = ( endY - startY ) * progress
@@ -302,10 +279,12 @@ const heroAnimation = async () => {
302
279
)
303
280
}
304
281
282
+ // Start animation when container is mounted
305
283
const observer = new MutationObserver ( ( ) => {
306
- if ( document . querySelector ( '.animation-container' ) ) {
284
+ const animContainer = document . querySelector ( '.animation-container' )
285
+ if ( animContainer ) {
307
286
observer . disconnect ( )
308
- heroAnimation ( )
287
+ heroAnimation ( animContainer )
309
288
}
310
289
} )
311
290
0 commit comments