css实现
css实现靠offset-path和offset-distance属性实现按路径运动,offset-path定义运动路径,offset-distance定义当前在路径的位置
.running-container {
width: 300px;
height: 300px;
position: relative;
}
.running-path {
position: absolute;
top: 0;
left: 0;
}
.car{
offset-path: path("M49.5,60.5s128-77,164-15,59,101,60,134-39,80-88,82-130-12-127-57-51-90-30-117Z");
animation: move 10s infinite linear;
width: 40px;
height: 40px;
background: url(https://assets.codepen.io/10903102/car.png) no-repeat 50%;
background-size: contain;
}
@keyframes move {
0% {
offset-distance: 0;
}
100% {
offset-distance: 100%;
}
}
<div class="running-container">
<svg viewBox="0 0 300 300" class="running-path">
<path d="M49.5,60.5s128-77,164-15,59,101,60,134-39,80-88,82-130-12-127-57-51-90-30-117Z" fill="transparent"
stroke="#000000" />
</svg>
<div class="car"></div>
</div>
效果
svg实现
svg实现靠animateMotion,其中rotate="auto"让其自动转向,不过明显看到其效果和css比,还是有点差距的
.car{
transform: translateY(-10px);
}
<svg viewBox="0 0 300 300" width="300" height="300">
<path fill="none" stroke="lightgrey"
d="M49.5,60.5s128-77,164-15,59,101,60,134-39,80-88,82-130-12-127-57-51-90-30-117Z" />
<image class="car" href="https://assets.codepen.io/10903102/car.png" width="40">
<animateMotion dur="10s" repeatCount="indefinite" rotate="auto" origin="default"
path="M49.5,60.5s128-77,164-15,59,101,60,134-39,80-88,82-130-12-127-57-51-90-30-117Z" />
</image>
</svg>
效果
canvas实现
canvas实现依赖svg的getTotalLength和getPointAtLength方法,这两个方法兼容性有待考量,如果需要支持比较老的浏览器,可能需要自己实现这两个方法
getTotalLength 用于计算路径的总长度 getPointAtLength 用于计算路径某个长度的坐标值
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
// 运动路径,用于绘制cavas的路径
const path = new Path2D('M49.5,60.5s128-77,164-15,59,101,60,134-39,80-88,82-130-12-127-57-51-90-30-117Z');
// 计算路径总长度
const svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
svgPath.setAttribute('d', 'M49.5,60.5s128-77,164-15,59,101,60,134-39,80-88,82-130-12-127-57-51-90-30-117Z')
const svgPathLen = svgPath.getTotalLength();
// 加载运动的小车
let image = null;
const request = new Request('https://assets.codepen.io/10903102/car.png');
fetch(request).then(response => {
if (response.ok) {
return response.blob()
}
}).then(blob => {
return createImageBitmap(blob);
}).then(bitImage => {
image = bitImage;
start();
})
// 用于绘制单个帧的方法
const draw = (l) => {
// 获得当前点、下个点和上个点的坐标
const point = svgPath.getPointAtLength(l);
const next = svgPath.getPointAtLength(l + 1);
const pre = svgPath.getPointAtLength(l - 1);
// 计算斜率以旋转小车
const dy = next.y - pre.y;
const dx = next.x - pre.x;
let angle;
if (dx > 0) {
angle = Math.atan(dy / dx)
} else {
angle = Math.PI + Math.atan(dy / dx)
}
// 清空画布
ctx.fillStyle = '#ffffff';
ctx.clearRect(0, 0, 300, 300);
// 绘制路径
ctx.strokeStyle = '#000000';
ctx.stroke(path);
//绘制小车
ctx.save();
const width = 32;
const height = width * image.height / image.width;
//小车移动到当前点,并设置旋转
ctx.translate(point.x, point.y);
ctx.rotate(angle)
ctx.drawImage(image, 0 - width/2, 0 - height/2, width, height);
ctx.restore();
}
function start() {
// 使用tween.js控制动画
const position = {i: 0};
const tween = new TWEEN.Tween(position).repeat(Infinity);
tween.to({i: svgPathLen}, 10000)
.onUpdate(() => {
draw(position.i)
})
tween.start();
animate()
function animate() {
requestAnimationFrame(animate)
TWEEN.update()
}
}
效果:代码用了tween.js可能需要科学上网才能运行