最后效果,下面代码是初始demo,逻辑都在
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<body>
<div class="page">
<canvas id="canvas"></canvas>
<div class="footer">
<div class="btn-box">
<div class="btn1">
摇一摇
</div>
</div>
</div>
</div>
<script src="./js/path.js"></script>
<script src="./js/canvas.js"></script>
<script src="./js/index.js"></script>
</body>
</body>
</html>
* {
margin: 0;
padding: 0;
}
.page {
width: 100vw;
height: 100vh;
position: relative;
}
#canvas {
position: absolute;
left: 0;
top: 0;
z-index: 1;
background-image: url(../image/bg.png);
background-repeat: no-repeat;
background-size: 100% 100%;
}
.footer {
position: absolute;
bottom: 0;
height: 100px;
z-index: 2;
}
.header{
width: 100%;
position: absolute;
top: 0;
height: 200px;
z-index: 2;
}
.header img{
width: 100%;
height: 100%;
}
.btn1 {
font-size: 50px;
}
canvas.js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// 节点大小
let nodeW = 15;
let nodeH = 8;
// 走过的路
let walkPath = 1;
let preyWalkPath = 1;
var totalTime = 500; // 运动总时间,单位为毫秒
var currentTime = 0; // 当前已经运动的时间,单位为毫秒
// 动画是否执行
let animateKg = true
// 图片路径数组
var imagePaths = [{
name: "car",
path: "./image/d1.png"
},
{
name: "node",
path: "./image/3.png"
},
{
name: "nodeA",
path: "./image/4.png"
},
];
let allImage = [];
// 获取设备像素比
var devicePixelRatio = window.devicePixelRatio || 1;
// 获取视口(viewport)的宽度和高度
var viewportWidth = document.documentElement.clientWidth;
var viewportHeight = document.documentElement.clientHeight;
// 设置Canvas元素的实际宽度和高度
canvas.width = viewportWidth * devicePixelRatio;
canvas.height = viewportHeight * devicePixelRatio;
canvas.style.width = "100%";
canvas.style.height = "100%";
let scaleX = viewportWidth / (375 * devicePixelRatio);
let scaleY = viewportHeight / (670 * devicePixelRatio);
var path = null;
path = JSON.parse(JSON.stringify(map1));
// 路径适配
path.forEach((item) => {
item.ex = item.ex * scaleX;
item.ey = item.ey * scaleY;
item.mx = item.mx * scaleX;
item.my = item.my * scaleY;
item.lx1 = item.lx1 * scaleX;
item.ly1 = item.ly1 * scaleY;
item.lx2 = item.lx2 * scaleX;
item.ly2 = item.ly2 * scaleY;
});
// 加载图片并返回 Promise 对象
function loadImage(image) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve({
name: image.name,
image: img
});
};
img.onerror = function() {
console.log(3);
reject(new Error("Failed to load image: " + image.path));
};
img.src = image.path;
});
}
// 创建多个图层
var layer1 = createLayer();
var layer2 = createLayer();
// 路径宽度
layer1.ctx.lineWidth = 3;
layer2.ctx.lineWidth = 3;
// 预加载所有图片
Promise.all(imagePaths.map(loadImage))
.then(function(images) {
// 图片加载完成后进行绘制操作
console.log(1);
allImage = images;
updata();
})
.catch(function(error) {
console.log(2);
console.error(error);
});
// 创建图层的函数
function createLayer() {
var layerCanvas = document.createElement("canvas");
layerCanvas.width = viewportWidth * devicePixelRatio;
layerCanvas.height = viewportHeight * devicePixelRatio;
layerCanvas.style.width = viewportWidth + "px";
layerCanvas.style.height = viewportHeight + "px";
var layerContext = layerCanvas.getContext("2d");
layerContext.scale(devicePixelRatio, devicePixelRatio);
return {
canvas: layerCanvas,
ctx: layerContext,
};
}
// 二次贝塞尔曲线函数
function quadraticBezierCurve(t, p0, p1, p2) {
// 计算当前位置
var currentBezierX =
(1 - t) * (1 - t) * p0.x + 2 * t * (1 - t) * p1.x + t * t * p2.x;
var currentBezierY =
(1 - t) * (1 - t) * p0.y + 2 * t * (1 - t) * p1.y + t * t * p2.y;
return {
x: currentBezierX,
y: currentBezierY,
};
}
// 绘制路径
function drawRoute(ex, ey, mx, my, lx1, ly1, lx2, ly2, index, kg, layer) {
if (index) {
// 曲线
layer.ctx.beginPath();
layer.ctx.moveTo(mx, my);
layer.ctx.globalCompositeOperation = "destination-over";
layer.ctx.quadraticCurveTo(lx1, ly1, lx2, ly2);
layer.ctx.stroke();
layer.ctx.closePath();
}
// 节点
layer.ctx.beginPath();
layer.ctx.globalCompositeOperation = "source-over";
let img = kg ? allImage[1].image : allImage[2].image;
layer.ctx.drawImage(img, ex - nodeW / 2, ey - nodeH / 2, nodeW, nodeH);
layer.ctx.fill();
layer.ctx.closePath();
}
// 绘制小车
function drawCar(x, y) {
// 在图像加载完成后绘制图像
layer2.ctx.globalCompositeOperation = "source-over";
layer2.ctx.drawImage(allImage[0].image, x - 20, y - 40, 40, 40);
}
// 遍历路径
function getPath(data, kg, layer) {
data.forEach((item, index) => {
drawRoute(
item.ex,
item.ey,
item.mx,
item.my,
item.lx1,
item.ly1,
item.lx2,
item.ly2,
index,
kg,
layer
);
});
}
// 绘制所有路径
function allPath() {
layer1.ctx.fillStyle = "white";
layer1.ctx.strokeStyle = "yellow";
getPath(path, true, layer1);
}
function overPath(index) {
layer2.ctx.globalCompositeOperation = "destination-over";
layer2.ctx.fillStyle = "red"; // 设置填充颜色
layer2.ctx.strokeStyle = "red"; // 设置边框颜色
getPath(path.slice(0, index), false, layer2);
}
// 绘制页面
function drawPage() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除上一次的绘制
layer1.ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除上一次的绘制
layer2.ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除上一次的绘制
allPath();
// 绘制走过路径
let pI = preyWalkPath - 1 ? preyWalkPath - 1 : 1;
overPath(pI);
if (currentTime >= totalTime - 16) {
overPath(preyWalkPath);
}
// 绘制车
var t = currentTime / totalTime;
// 计算贝塞尔曲线上的点
const point = quadraticBezierCurve(
t, {
x: path[preyWalkPath - 1].mx,
y: path[preyWalkPath - 1].my,
}, {
x: path[preyWalkPath - 1].lx1,
y: path[preyWalkPath - 1].ly1,
}, {
x: path[preyWalkPath - 1].lx2,
y: path[preyWalkPath - 1].ly2,
}
);
drawCar(point.x, point.y);
ctx.drawImage(layer1.canvas, 0, 0);
ctx.drawImage(layer2.canvas, 0, 0);
}
function updata() {
drawPage();
// 请求下一帧动画
// 更新当前时间
currentTime += 16; // 假设每帧间隔为16毫秒
// 判断是否继续动画
if (currentTime < totalTime) {
requestAnimationFrame(updata);
} else {
if (preyWalkPath < walkPath) {
currentTime = 0;
requestAnimationFrame(updata);
preyWalkPath++;
} else {
// 动画结束
animateKg = true
}
}
}
index.js
let btn1=document.querySelector('.btn1')
btn1.onclick=()=>{
if(animateKg){
animateKg=false
let random=Math.floor(Math.random()*2)+1;
preyWalkPath=walkPath+1
walkPath+=random;
currentTime=0;
updata()
}
}
path.js
let map1 = [{
ex: 100,
ey: 500,
mx: 100,
my: 500,
lx1: 100,
ly1: 500,
lx2: 100,
ly2: 500
},
{
ex: 140,
ey: 510,
mx: 100,
my: 500,
lx1: 120,
ly1: 515,
lx2: 140,
ly2: 508
},
{
ex: 180,
ey: 510,
mx: 140,
my: 510,
lx1: 160,
ly1: 515,
lx2: 180,
ly2: 508
},
{
ex: 220,
ey: 500,
mx: 180,
my: 508,
lx1: 200,
ly1: 510,
lx2: 220,
ly2: 498
},
{
ex: 260,
ey: 470,
mx: 220,
my: 500,
lx1: 260,
ly1: 500,
lx2: 260,
ly2: 470
},
{
ex: 220,
ey: 450,
mx: 260,
my: 470,
lx1: 240,
ly1: 440,
lx2: 220,
ly2: 452
},
{
ex: 180,
ey: 430,
mx: 220,
my: 450,
lx1: 200,
ly1: 500,
lx2: 180,
ly2: 430
},
{
ex: 230,
ey: 400,
mx: 180,
my: 430,
lx1: 190,
ly1: 350,
lx2: 230,
ly2: 400
}
]