Skip to content

Commit

Permalink
fix(react-bgs): Dots rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
romelperez committed Apr 27, 2024
1 parent 8194a68 commit 0127be7
Showing 1 changed file with 112 additions and 95 deletions.
207 changes: 112 additions & 95 deletions packages/react-bgs/src/Dots/Dots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { easing } from '@arwes/animated'
import { type DotsProps } from './Dots.types.js'
import { getDistanceFromOriginToCornerProgress } from './getDistanceFromOriginToCornerProgress.js'

const { entering, exiting } = ANIMATOR_STATES
const { entering, entered, exiting } = ANIMATOR_STATES

const defaultProps: Required<
Pick<DotsProps, 'color' | 'type' | 'distance' | 'size' | 'crossSize' | 'origin' | 'easing'>
Expand All @@ -36,17 +36,17 @@ const Dots = (props: DotsProps): ReactElement => {
useEffect(() => {
const canvas = elementRef.current

if (!animator || !canvas) {
if (!canvas) {
return
}

let animationControl: ReturnType<typeof animate> | undefined
let resizeObserver: ResizeObserver | undefined

const ctx = canvas.getContext('2d')!
const dpr = window.devicePixelRatio || 2
const dpr = Math.min(window.devicePixelRatio || 2, 2)

const setupCanvasSize = (): void => {
const resize = (): void => {
const { width, height } = canvas.getBoundingClientRect()

if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
Expand All @@ -58,119 +58,136 @@ const Dots = (props: DotsProps): ReactElement => {
ctx.scale(dpr, dpr)
}

setupCanvasSize()
const draw = (isEntering: boolean, progress: number): void => {
const { color, type, distance, size, crossSize, origin, originInverted } =
propsFullRef.current

const cancelAnimationSubscriptions = (): void => {
animationControl?.cancel()
resizeObserver?.disconnect()
}
const { width, height } = canvas

const animatorSubscription = (node: AnimatorNode): void => {
if (node.state !== entering && node.state !== exiting) {
return
}

cancelAnimationSubscriptions()
const xLength = 1 + Math.floor(width / distance)
const yLength = 1 + Math.floor(height / distance)

const active = node.state === entering
const { duration } = node.control.getSettings()
const transitionDuration = (active ? duration?.enter : duration?.exit) || 0
const xMargin = width % distance
const yMargin = height % distance

const draw = (progress: number): void => {
const { color, type, distance, size, crossSize, origin, originInverted } =
propsFullRef.current
ctx.clearRect(0, 0, width, height)

const { width, height } = canvas
for (let xIndex = 0; xIndex < xLength; xIndex++) {
const x = xMargin / 2 + xIndex * distance

const xLength = 1 + Math.floor(width / distance)
const yLength = 1 + Math.floor(height / distance)
for (let yIndex = 0; yIndex < yLength; yIndex++) {
const y = yMargin / 2 + yIndex * distance

const xMargin = width % distance
const yMargin = height % distance
const distanceFromOriginProgress = getDistanceFromOriginToCornerProgress(
width / dpr,
height / dpr,
x,
y,
origin
)

ctx.clearRect(0, 0, width, height)
const distancePercentage =
(isEntering && originInverted) || (!isEntering && !originInverted)
? 1 - distanceFromOriginProgress
: distanceFromOriginProgress

for (let xIndex = 0; xIndex < xLength; xIndex++) {
const x = xMargin / 2 + xIndex * distance
const alphaProgress = progress / distancePercentage
const alpha = Math.max(0, Math.min(1, alphaProgress))

for (let yIndex = 0; yIndex < yLength; yIndex++) {
const y = yMargin / 2 + yIndex * distance
ctx.beginPath()
ctx.globalAlpha = isEntering ? alpha : 1 - alpha

const distanceFromOriginProgress = getDistanceFromOriginToCornerProgress(
width / dpr,
height / dpr,
x,
y,
origin
)
if (type === 'circle') {
ctx.arc(x, y, size, 0, 2 * Math.PI)
}
//
else if (type === 'cross') {
const l = size / 2
const b = crossSize / 2

// left
ctx.moveTo(x - l, y + b)
ctx.lineTo(x - l, y - b)
ctx.lineTo(x - b, y - b)

// top
ctx.lineTo(x - b, y - l)
ctx.lineTo(x + b, y - l)
ctx.lineTo(x + b, y - b)

// right
ctx.lineTo(x + l, y - b)
ctx.lineTo(x + l, y + b)
ctx.lineTo(x + b, y + b)

// bottom
ctx.lineTo(x + b, y + l)
ctx.lineTo(x - b, y + l)
ctx.lineTo(x - b, y + b)
}
//
else {
ctx.rect(x - size / 2, y - size / 2, size, size)
}

const distancePercentage =
(active && originInverted) || (!active && !originInverted)
? 1 - distanceFromOriginProgress
: distanceFromOriginProgress
ctx.fillStyle = color
ctx.fill()
ctx.closePath()
}
}
}

const alphaProgress = progress / distancePercentage
const alpha = Math.max(0, Math.min(1, alphaProgress))
resize()

ctx.beginPath()
ctx.globalAlpha = active ? alpha : 1 - alpha
if (window.ResizeObserver) {
resizeObserver = new window.ResizeObserver(() => {
resize()

if (type === 'circle') {
ctx.arc(x, y, size, 0, 2 * Math.PI)
}
//
else if (type === 'cross') {
const l = size / 2
const b = crossSize / 2

// left
ctx.moveTo(x - l, y + b)
ctx.lineTo(x - l, y - b)
ctx.lineTo(x - b, y - b)

// top
ctx.lineTo(x - b, y - l)
ctx.lineTo(x + b, y - l)
ctx.lineTo(x + b, y - b)

// right
ctx.lineTo(x + l, y - b)
ctx.lineTo(x + l, y + b)
ctx.lineTo(x + b, y + b)

// bottom
ctx.lineTo(x + b, y + l)
ctx.lineTo(x - b, y + l)
ctx.lineTo(x - b, y + b)
}
//
else {
ctx.rect(x - size / 2, y - size / 2, size, size)
if (animator) {
switch (animator.node.state) {
case entered: {
draw(true, 1)
break
}

ctx.fillStyle = color
ctx.fill()
ctx.closePath()
}
} else {
draw(true, 1)
}
}

animationControl = animate(draw, {
duration: transitionDuration,
easing: propsFullRef.current.easing
})

if (window.ResizeObserver) {
resizeObserver = new window.ResizeObserver(() => {
setupCanvasSize()
resizeObserver.observe(canvas)
}

const currentTime = animationControl?.currentTime || 0
if (active && currentTime >= transitionDuration) {
draw(1)
}
})
const cancelAnimationSubscriptions = (): void => {
animationControl?.cancel()
resizeObserver?.disconnect()
}

if (!animator) {
return () => {
cancelAnimationSubscriptions()
}
}

resizeObserver.observe(canvas)
const animatorSubscription = (node: AnimatorNode): void => {
switch (node.state) {
case entering: {
cancelAnimationSubscriptions()
animationControl = animate((progress) => draw(true, progress), {
duration: node.duration.enter,
easing: propsFullRef.current.easing
})
break
}

case exiting: {
cancelAnimationSubscriptions()
animationControl = animate((progress) => draw(false, progress), {
duration: node.duration.exit,
easing: propsFullRef.current.easing
})
break
}
}
}

Expand Down

0 comments on commit 0127be7

Please sign in to comment.