Skip to content

Commit d55d9a0

Browse files
jesseabordenJesse Borden
andauthored
feat(hero-anim): Add mobile hero animation logic. Misc cleanup relate… (#1025)
* feat(hero-anim): Add mobile hero animation logic. Misc cleanup related to hero animation. * chor(hero): simplify animation offset calc. * chor(hero): simplify animation offset calc. * fix(hero): broken var. --------- Co-authored-by: Jesse Borden <jborden@apple.com>
1 parent 0949d27 commit d55d9a0

File tree

2 files changed

+65
-86
lines changed

2 files changed

+65
-86
lines changed

assets/javascripts/new-javascripts/hero.js

Lines changed: 64 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const heroAnimation = async () => {
1+
const heroAnimation = async (animContainer) => {
22
const isReduceMotionEnabled = window.matchMedia(
33
'(prefers-reduced-motion: reduce)',
44
).matches
@@ -14,6 +14,10 @@ const heroAnimation = async () => {
1414
})
1515
}
1616

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+
1721
const heroSwoops = [
1822
{
1923
canvas: document.querySelector('#purple-swoop'),
@@ -25,7 +29,7 @@ const heroAnimation = async () => {
2529
lineWidth: 210,
2630
debugColor: 'purple',
2731
image: null,
28-
state: { progress: 0 },
32+
state: { progress: offScreenDelta },
2933
},
3034
{
3135
canvas: document.querySelector('#white-swoop-1'),
@@ -37,7 +41,7 @@ const heroAnimation = async () => {
3741
lineWidth: 140,
3842
debugColor: 'red',
3943
image: null,
40-
state: { progress: 0 },
44+
state: { progress: offScreenDelta },
4145
},
4246
{
4347
canvas: document.querySelector('#white-swoop-2'),
@@ -49,7 +53,7 @@ const heroAnimation = async () => {
4953
lineWidth: 73.6,
5054
debugColor: 'cyan',
5155
image: null,
52-
state: { progress: 0 },
56+
state: { progress: offScreenDelta },
5357
},
5458
{
5559
canvas: document.querySelector('#orange-swoop-bottom'),
@@ -61,7 +65,7 @@ const heroAnimation = async () => {
6165
lineWidth: 202.2,
6266
debugColor: 'yellow',
6367
image: null,
64-
state: { progress: 0 },
68+
state: { progress: offScreenDelta },
6569
},
6670
{
6771
canvas: document.querySelector('#orange-swoop-top'),
@@ -73,7 +77,7 @@ const heroAnimation = async () => {
7377
lineWidth: 163.4,
7478
debugColor: 'green',
7579
image: null,
76-
state: { progress: 0 },
80+
state: { progress: offScreenDelta },
7781
},
7882
]
7983
const logo = {
@@ -84,8 +88,9 @@ const heroAnimation = async () => {
8488
position: [610, 672.5],
8589
imagePath: '/assets/images/landing-page/hero/bird.png',
8690
image: null,
87-
state: { progress: 0 },
91+
state: { progress: offScreenDelta },
8892
}
93+
8994
const initSwoops = ({
9095
path,
9196
pathLength,
@@ -96,13 +101,12 @@ const heroAnimation = async () => {
96101
image,
97102
}) => {
98103
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
100105
// We're not going to reset this as it will make pulling values directly from AE easier
101106
ctx.translate(posX - image.naturalWidth / 2, posY - image.naturalHeight / 2)
102107
// Set mask styles
103108
ctx.lineWidth = lineWidth
104109
ctx.lineCap = 'round'
105-
// Convert SVG path pulled from AE masks
106110
let pathInstance = new Path2D(path)
107111

108112
if (!isReduceMotionEnabled) {
@@ -127,13 +131,10 @@ const heroAnimation = async () => {
127131
positionEnd: [endX, endY],
128132
}) => {
129133
const ctx = canvas.getContext('2d')
130-
// Same reason for conversion as initSwoops
134+
// Applying this conversion for the same purpose as init swoops
131135
ctx.translate(posX - image.naturalWidth / 2, posY - image.naturalHeight / 2)
132136

133-
if (!isReduceMotionEnabled) {
134-
ctx.globalAlpha = 0
135-
ctx.drawImage(image, 0, 0)
136-
} else {
137+
if (isReduceMotionEnabled) {
137138
ctx.globalAlpha = 1
138139
const deltaX = endX - posX
139140
const deltaY = endY - posY
@@ -144,144 +145,119 @@ const heroAnimation = async () => {
144145
}
145146

146147
try {
147-
// load swoop image
148+
// Load swoop images
148149
const swoopImages = await Promise.all(
149150
heroSwoops.map((swoop) => loadImage(swoop.imagePath)),
150151
)
151-
// load logo
152+
// Load logo
152153
const logoImage = await loadImage(logo.imagePath)
153154

154155
logo.image = logoImage
155-
// init canvas for each swoop layer
156+
// Init canvas for each swoop layer
156157
heroSwoops.forEach((swoop, i) => {
157158
swoop.image = swoopImages[i]
158159
const canvasData = initSwoops(swoop)
159160
swoop.ctx = canvasData.ctx
160161
swoop.pathInstance = canvasData.pathInstance
161162
})
162-
// init logo canvas
163+
// Init logo canvas
163164
logo.ctx = initLogo(logo)
164165
} catch (error) {
165166
console.error('Error loading images:', error)
166167
throw error
167168
}
168169

170+
// Skip animation if reduced motion is enabled
169171
if (isReduceMotionEnabled) {
170172
return
171173
}
172174

173-
const DURATION = 1000
175+
const DURATION = 1000 - 1000 * offScreenDelta
174176

175177
const tl = anime.createTimeline({
176178
defaults: { duration: DURATION, ease: 'inOut(.8)' },
177179
})
178180

179181
tl.label('start', 0)
180182

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
182205
tl.add(
183206
heroSwoops[1].state,
184207
{
185208
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]),
197211
},
198212
'start',
199213
)
200-
// // purple swoop
214+
// Purple swoop
201215
tl.add(
202216
heroSwoops[0].state,
203217
{
204218
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]),
216221
},
217222
'start',
218223
)
219-
// // white swoop 2 swoop
224+
// White swoop 2
220225
tl.add(
221226
heroSwoops[2].state,
222227
{
223228
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]),
234230
},
235231
'start',
236232
)
237-
// // orange swoop bottom
233+
// Orange swoop bottom
238234
tl.add(
239235
heroSwoops[3].state,
240236
{
241237
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]),
252239
},
253240
'start',
254241
)
255-
// orange top
242+
// Orange top
256243
tl.add(
257244
heroSwoops[4].state,
258245
{
259246
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]),
273250
},
274251
'start',
275252
)
276-
// logo
253+
// Logo
277254
tl.add(
278255
logo.state,
279256
{
280257
ease: 'out(1.1)',
281-
duration: 200,
282-
delay: 750,
258+
duration: 200 - 200 * offScreenDelta,
259+
delay: 750 - 750 * offScreenDelta,
283260
progress: 1,
284-
// ease: 'inOutQuad',
285261
onUpdate: () => {
286262
const {
287263
state: { progress },
@@ -292,6 +268,7 @@ const heroAnimation = async () => {
292268
positionEnd: [endX, endY],
293269
} = logo
294270
ctx.clearRect(0, 0, canvas.width, canvas.height)
271+
// Progresses logo opacity from 0 to 1
295272
ctx.globalAlpha = progress
296273
const deltaX = (endX - startX) * progress
297274
const deltaY = (endY - startY) * progress
@@ -302,10 +279,12 @@ const heroAnimation = async () => {
302279
)
303280
}
304281

282+
// Start animation when container is mounted
305283
const observer = new MutationObserver(() => {
306-
if (document.querySelector('.animation-container')) {
284+
const animContainer = document.querySelector('.animation-container')
285+
if (animContainer) {
307286
observer.disconnect()
308-
heroAnimation()
287+
heroAnimation(animContainer)
309288
}
310289
})
311290

assets/stylesheets/new-stylesheets/pages/_index.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ $icons: (
3636
}
3737

3838
.animation-container {
39-
@include noise();
39+
aspect-ratio: 1248 / 1116;
4040
top: calc(66px - 9.5vw);
4141
left: 0;
4242
width: 57vw;

0 commit comments

Comments
 (0)