1. CSS
First, the first method is the CSS way.
Using offset-path
, and employing animation
to make offset-distance
go from 0% to 100%.
offset-path: path('M179.43,103.86 ...');
animation: move 3000ms infinite ease-in-out;
@keyframes move {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
2. SVG
The second, SVG
<path
fill='none'
stroke='lightgrey'
d='M179.43,103.86 ...'
/>
<rect width="16" height="16" fill='rgb(75, 85, 99)'>
<animateMotion
dur='3s'
rotate='auto'
repeatCount='indefinite'
path='M179.43,103.86 ...'
calcMode='spline'
keyTimes='0; 1'
keySplines='0.5 0 0.5 1'
/>
</rect>
3. JS
The last is control through JavaScript code.
import { animate } from 'motion'
import { useEffect, useRef } from 'react'
import { getPointAtLength, getTotalLength } from 'svg-path-commander'
const d = 'M179.43,103.86 ... '
const pathLength = getTotalLength(d)
export default function JSMotion() {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (ref.current) {
animate(0, 100, {
repeat: Infinity,
ease: 'easeInOut',
onUpdate: latest => {
const currentLength = (latest / 100) * pathLength
const { x: currentX, y: currentY } = getPointAtLength(d, currentLength)
ref.current!.style.left = currentX + 'px'
ref.current!.style.top = currentY + 'px'
const { x: nextX, y: nextY } = getPointAtLength(d, currentLength + 1)
const dx = nextX - currentX
const dy = nextY - currentY
const angle = Math.atan2(dy, dx) * (180 / Math.PI) + 90
ref.current!.style.rotate = angle + 'deg'
},
duration: 3
})
}
}, [ref.current])
return (
<div className=' relative w-full h-[400px] bg-gray-50 rounded border'>
<div ref={ref} className='w-4 h-4 bg-gray-600 absolute'></div>
</div>
)
}
3.1 JS - native
When using JavaScript to calculate points, it takes about 0.05 seconds for each session, and the performance is unstable, leading to unsmooth operation. However, using the native methods of SVG can improve the efficiency by 1000 times.
const pathElement = document.getElementById('path') as any as SVGPathElement
if (!pathElement) return
const pathLength = pathElement.getTotalLength()
currentAnimation = animate(0, 100, {
ease: 'linear',
onUpdate: latest => {
const currentLength = (latest / 100) * pathLength
const { x: currentX, y: currentY } = pathElement.getPointAtLength(currentLength)
ref.current!.style.left = currentX + 'px'
ref.current!.style.top = currentY + 'px'
const nextPoint = pathElement.getPointAtLength(currentLength + 1)
const angle = Math.atan2(nextPoint.y - currentY, nextPoint.x - currentX) * (180 / Math.PI) + 90
ref.current!.style.rotate = angle - 90 + 'deg'
},
duration: pathLength / +speed,
onComplete() {
ref.current!.style.display = 'none'
}
})
Experience
CSS is relatively simple and can be directly applied in a normal HTML environment. The SVG approach, however, relies on a stable SVG environment, where the size is scaled according to the SVG dimensions. Fine-tuning the easing effect requires using keySplines
, which demands some careful adjustment.
The movement in CSS is similar to a relative offset effect. In contrast, SVG involves setting x and y values within the SVG environment.
For JavaScript, the implementation depends on an animate
library, a path utility library (svg-path-commander
), and some calculations.
For simple loop animations or single-play animations, CSS is more convenient and efficient. SVG can quickly scale according to the page size.
JavaScript, on the other hand, can orchestrate multiple playback logics and is suitable for complex animations.