Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

Commit

Permalink
[PAY-1632] Clean up and improve performance of music confetti (#3921)
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondjacobson authored Aug 22, 2023
1 parent 7ed2248 commit 0025261
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 241 deletions.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions packages/web/src/assets/img/particles/particleNotePrimary.svg

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

239 changes: 70 additions & 169 deletions packages/web/src/components/background-animations/MusicConfetti.tsx
Original file line number Diff line number Diff line change
@@ -1,202 +1,105 @@
import { useRef, useState, useEffect, useCallback } from 'react'
import { useRef, useCallback, useEffect, useState } from 'react'

import heartIconPrimary from 'assets/img/particles/particleHeartPrimary.svg'
import heartIconSecondary from 'assets/img/particles/particleHeartSecondary.svg'
import listensIconPrimary from 'assets/img/particles/particleListensPrimary.svg'
import listensIconSecondary from 'assets/img/particles/particleListensSecondary.svg'
import noteIconPrimary from 'assets/img/particles/particleNotePrimary.svg'
import noteIconSecondary from 'assets/img/particles/particleNoteSecondary.svg'
import playlistsIconPrimary from 'assets/img/particles/particlePlaylistPrimary.svg'
import playlistsIconSecondary from 'assets/img/particles/particlePlaylistSecondary.svg'
import Confetti from 'utils/animations/music-confetti'
import { useOnResizeEffect } from 'utils/effects'

const DEFAULT_IMAGES = [
heartIconPrimary,
heartIconSecondary,
noteIconSecondary,
noteIconPrimary,
listensIconPrimary,
listensIconSecondary,
playlistsIconPrimary,
playlistsIconSecondary
]

async function startConfettiAnimation(
canvasRef: HTMLCanvasElement,
recycle: boolean,
limit: number,
friction: number,
gravity: number,
rotate: number,
swing: number,
particleRate: number,
onCompletion: () => void,
isMatrix: boolean
) {
if (!canvasRef) return
let images = null
if (!isMatrix) {
images = DEFAULT_IMAGES.map((icon) => {
const img = new Image()
img.src = icon
return img
})
await Promise.all(
images.map(
(img) =>
new Promise((resolve) => {
img.onload = () => {
resolve(true)
}
})
)
)
}

const confetti = new Confetti(
canvasRef,
images,
recycle,
limit,
friction,
gravity,
rotate,
swing,
particleRate,
onCompletion
)
confetti.run()
return confetti
}
import { Theme } from '@audius/common'

type MusicConfettiProps = {
withBackground?: boolean
recycle?: boolean
limit?: number
friction?: number
gravity?: number
rotate?: number
swing?: number
particleRate?: number
zIndex?: number
onCompletion?: () => void
isMatrix?: boolean
}
import Confetti from 'utils/animations/music-confetti'
import { getCurrentThemeColors } from 'utils/theme/theme'

const defaultProps: MusicConfettiProps = {
withBackground: false,
recycle: false,
limit: 250,
const DEFAULTS = {
limit: 200,
friction: 0.99,
gravity: 0.2,
rotate: 0.1,
swing: 0.01,
particleRate: 0.1,
zIndex: 16,
onCompletion: () => {},
isMatrix: false
particleRate: 0.1
}

const MusicConfetti = (props: MusicConfettiProps) => {
const newProps = { ...props } as Required<MusicConfettiProps>

if (props.isMatrix) {
newProps.swing = 0
newProps.rotate = 0
}
const PATHS = [
// Heart
new Path2D(
'M4.8294,0 C1.83702857,0 0,2.65379464 0,4.61012277 C0,8.84082589 5.27554286,12.500625 9,15 C12.7244571,12.4996875 18,8.84082589 18,4.61012277 C18,2.65363058 16.1638457,0 13.1706,0 C11.4991714,0 10.0707429,1.21490625 9,2.36835938 C7.92822857,1.21478906 6.50088,0 4.8294,0 Z'
),
// Listens
new Path2D(
'M9.01215343,0 C4.03768506,0 0,4.14271233 0,9.24657534 L0,13.9315068 C0.0244073147,16.175737 1.82684761,18 4.013172,18 L4.54169274,18 C4.997924,18 5.38282706,17.6050849 5.38282706,17.1369863 L5.38282706,10.6027397 C5.38282706,10.1346411 4.997924,9.73972603 4.54169274,9.73972603 L4.013172,9.73972603 C3.14762075,9.73972603 2.33090336,10.0354192 1.65780365,10.5285699 L1.65780365,9.24657534 C1.65780365,5.10386301 4.95000337,1.7260274 8.98768843,1.7260274 C13.0253735,1.7260274 16.3420863,5.10386301 16.3420863,9.24657534 L16.3420863,10.5285699 C15.6689866,10.0354192 14.8757248,9.73972603 13.9867179,9.73972603 L13.4581972,9.73972603 C13.0019659,9.73972603 12.6170629,10.1346411 12.6170629,10.6027397 L12.6170629,17.1369863 C12.6170629,17.6050849 13.0019659,18 13.4581972,18 L13.9867179,18 C16.1975073,18 17.9765785,16.175737 17.9998899,13.9315068 L17.9998899,9.24657534 C18.0242972,4.14271233 13.9867179,0 9.01224955,0 L9.01215343,0 Z'
),
// Note
new Path2D(
'M3,13.0400072 L3,3.61346039 C3,3.16198653 3.28424981,2.76289841 3.70172501,2.62823505 L12.701725,0.0477059646 C13.3456556,-0.160004241 14,0.3365598 14,1.0329313 L14,12.9033651 C14,12.9179063 13.9997087,12.9323773 13.9991318,12.9467722 C13.9997094,12.9644586 14,12.9822021 14,13 C14,14.1045695 12.8807119,15 11.5,15 C10.1192881,15 9,14.1045695 9,13 C9,11.8954305 10.1192881,11 11.5,11 C11.6712329,11 11.838445,11.0137721 12,11.0400072 L12,3.61346039 L5,5.5488572 L5,14.9677884 C5,14.9726204 4.99996783,14.9774447 4.99990371,14.9822611 C4.99996786,14.988168 5,14.994081 5,15 C5,16.1045695 3.88071187,17 2.5,17 C1.11928813,17 0,16.1045695 0,15 C0,13.8954305 1.11928813,13 2.5,13 C2.67123292,13 2.83844503,13.0137721 3,13.0400072 Z'
),
// Playlists
new Path2D(
'M5.46563786,16.9245466 C4.92388449,17.5744882 4.02157241,18 3,18 C1.34314575,18 0,16.8807119 0,15.5 C0,14.1192881 1.34314575,13 3,13 C3.35063542,13 3.68722107,13.0501285 4,13.1422548 L4,3.02055066 C4,2.49224061 4.31978104,2.02523181 4.78944063,1.86765013 L10.5394406,0.0558250276 C11.2638626,-0.187235311 12,0.393838806 12,1.20872555 C12,2.01398116 12,2.61792286 12,3.02055066 C12,3.62449236 11.4511634,4.01020322 11,4.12121212 C10.3508668,4.28093157 8.68420009,4.62436591 6,5.15151515 L6,16.0224658 C6,16.5009995 5.80514083,16.7960063 5.46563786,16.9245466 Z M13,6 L17,6 C17.5522847,6 18,6.44771525 18,7 C18,7.55228475 17.5522847,8 17,8 L13,8 C12.4477153,8 12,7.55228475 12,7 C12,6.44771525 12.4477153,6 13,6 Z M11,10 L17,10 C17.5522847,10 18,10.4477153 18,11 C18,11.5522847 17.5522847,12 17,12 L11,12 C10.4477153,12 10,11.5522847 10,11 C10,10.4477153 10.4477153,10 11,10 Z M11,14 L17,14 C17.5522847,14 18,14.4477153 18,15 C18,15.5522847 17.5522847,16 17,16 L11,16 C10.4477153,16 10,15.5522847 10,15 C10,14.4477153 10.4477153,14 11,14 Z'
)
]

return <UnconditionalMusicConfetti {...newProps} />
type MusicConfettiProps = {
withBackground?: boolean
zIndex?: number
onCompletion?: () => void
theme?: Theme
isMobile?: boolean
}

MusicConfetti.defaultProps = defaultProps
export const MusicConfetti = ({
zIndex,
withBackground,
onCompletion,
theme,
isMobile
}: MusicConfettiProps) => {
const confettiRef = useRef<Confetti | null>(null)
const [colors, setColors] = useState<string[]>([
getCurrentThemeColors()['--primary'],
getCurrentThemeColors()['--secondary']
])
const isMatrix = theme === Theme.MATRIX

const UnconditionalMusicConfetti = (props: Required<MusicConfettiProps>) => {
const confettiPromiseRef = useRef<Promise<Confetti | undefined> | null>(null)

// When we mount canvas, start confetti and set the confettiPromiseRef
const {
recycle,
limit,
friction,
gravity,
rotate,
swing,
particleRate,
onCompletion,
isMatrix
} = props
useEffect(() => {
setColors([
getCurrentThemeColors()['--primary'],
getCurrentThemeColors()['--secondary']
])
}, [theme])

// When we mount canvas, start confetti and set the confettiRef
const setCanvasRef = useCallback(
(node: HTMLCanvasElement) => {
if (!node) return
const confetti = startConfettiAnimation(

const confetti = new Confetti(
node,
recycle,
limit,
friction,
gravity,
rotate,
swing,
particleRate,
onCompletion,
isMatrix
isMatrix ? undefined : PATHS,
isMatrix ? undefined : colors,
false,
isMatrix ? (isMobile ? 200 : 500) : DEFAULTS.limit,
DEFAULTS.friction,
isMatrix ? 0.25 : DEFAULTS.gravity,
isMatrix ? 0 : DEFAULTS.rotate,
isMatrix ? 0 : DEFAULTS.swing,
DEFAULTS.particleRate,
onCompletion
)
confettiPromiseRef.current = confetti
confetti.run()
confettiRef.current = confetti
},
[
recycle,
limit,
friction,
gravity,
rotate,
swing,
particleRate,
onCompletion,
isMatrix
]
[onCompletion, isMatrix, isMobile, colors]
)

// When we unmount, stop the canvas animation
useEffect(() => {
return () => {
if (confettiPromiseRef.current) {
confettiPromiseRef.current.then((animation) =>
animation ? animation.stop() : null
)
}
}
}, [])

const [sizing, setSize] = useState({})

useEffect(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight
})
}, [])

useOnResizeEffect(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight
})
})

return (
<canvas
ref={setCanvasRef}
{...sizing}
width={window.innerWidth}
height={window.innerHeight}
style={{
pointerEvents: 'none',
position: 'absolute',
top: 0,
left: 0,
zIndex: props.zIndex,
zIndex,
width: '100vw',
height: '100vh',
...(props.withBackground
...(withBackground
? {
background:
'radial-gradient(circle at top left, #B749D6 0%, #6516A3 100%)'
Expand All @@ -206,5 +109,3 @@ const UnconditionalMusicConfetti = (props: Required<MusicConfettiProps>) => {
/>
)
}

export default MusicConfetti
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { useCallback } from 'react'

import { musicConfettiActions, musicConfettiSelectors } from '@audius/common'
import {
musicConfettiActions,
musicConfettiSelectors,
themeSelectors,
Theme
} from '@audius/common'
import { useDispatch } from 'react-redux'

import MusicConfetti from 'components/background-animations/MusicConfetti'
import { MusicConfetti } from 'components/background-animations/MusicConfetti'
import { useIsMobile } from 'utils/clientUtil'
import { useSelector } from 'utils/reducer'
import { isMatrix } from 'utils/theme/theme'
import zIndex from 'utils/zIndex'

// Re-enable for easy debugging
// import useHotkeys from 'hooks/useHotkey'
// const { show } = musicConfettiActions

const { getTheme } = themeSelectors
const { hide } = musicConfettiActions
const { getIsVisible } = musicConfettiSelectors

Expand All @@ -17,21 +27,23 @@ const ConnectedMusicConfetti = () => {
dispatch(hide())
}, [dispatch])

// Re-enable for easy debugging
// useHotkeys({
// 88: () => dispatch(show())
// })

const isVisible = useSelector(getIsVisible)
const isMatrixMode = isMatrix()
const isMobile = useIsMobile()
const theme = useSelector(getTheme)

return isVisible ? (
<MusicConfetti
zIndex={10000}
zIndex={zIndex.MUSIC_CONFETTI}
onCompletion={onConfettiFinished}
isMatrix={isMatrixMode}
limit={isMatrixMode ? (isMobile ? 200 : 500) : undefined}
gravity={isMatrixMode ? 0.25 : undefined}
theme={theme || Theme.DEFAULT}
isMobile={isMobile}
/>
) : (
<></>
)
) : null
}

export default ConnectedMusicConfetti
Loading

0 comments on commit 0025261

Please sign in to comment.