1、先看效果
1、标记点波动效果
2、png图片波动效果
2、废话不多说直接上代码
js
<template>
<div ref="containerRef" class="flex items-center justify-center w-full h-full overflow-hidden bg-#eee">
<canvas ref="canvasRef" class="w-full h-full cursor-grab block"></canvas>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, inject, computed, defineExpose } from 'vue';
const canvasRef = ref(null);
const containerRef = ref(null);
const imgSrc = 'xxx'; // 图片路径
let ctx, img;
let scale = 1; // 初始缩放比例
let minScale = 0.5; // 限制缩放范围
let maxScale = 8; // 限制缩放范围
let position = { x: 0, y: 0 }; // 画布偏移
let startDrag = null;
let animationFrameId = null;
// **标记点**(WebSocket 接收)
const markers = ref([{ x: 100, y: 150, pulse: 0 }]); // pulse 控制动画
// **绘制图片**
// const drawImage = () => {
// if (!ctx || !img) return;
// const { width: boxWidth, height: boxHeight } = containerRef.value.getBoundingClientRect();
// canvasRef.value.width = boxWidth;
// canvasRef.value.height = boxHeight;
// ctx.clearRect(0, 0, boxWidth, boxHeight);
// ctx.save();
// ctx.translate(position.x, position.y);
// ctx.scale(scale, scale);
// ctx.drawImage(img, 0, 0, img.width, img.height);
// // **绘制标记点**
// markers.value.forEach((marker) => {
// marker.pulse += 0.05;
// const dynamicRadius = 5 + Math.sin(marker.pulse) * 2;
// const dynamicAlpha = 0.6 + Math.abs(Math.sin(marker.pulse)) * 0.4;
// ctx.beginPath();
// ctx.arc(marker.x, marker.y, dynamicRadius / scale, 0, 2 * Math.PI);
// ctx.fillStyle = `rgba(255, 0, 0, ${dynamicAlpha})`;
// // ctx.arc(marker.x, marker.y, 5 / scale, 0, 2 * Math.PI);
// // ctx.fillStyle = 'red';
// ctx.fill();
// ctx.closePath();
// });
// ctx.restore();
// animationFrameId = requestAnimationFrame(drawImage);
// };
// **创建标记点图片**
const markerImg = new Image();
markerImg.src = 'xx'; // 确保路径正确
markerImg.onload = () => {
console.log('标记点图片加载完成');
};
const drawImage = () => {
if (!ctx || !img || !markerImg.complete) return; // 确保所有图片都加载完成
const { width: boxWidth, height: boxHeight } = containerRef.value.getBoundingClientRect();
canvasRef.value.width = boxWidth;
canvasRef.value.height = boxHeight;
ctx.clearRect(0, 0, boxWidth, boxHeight);
ctx.save();
ctx.translate(position.x, position.y);
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0, img.width, img.height);
// **绘制标记点**
markers.value.forEach((marker) => {
marker.pulse += 0.05;
const dynamicScale = 1 + Math.sin(marker.pulse) * 0.2; // 让图片动态变化
const imgSize = 20 * dynamicScale; // 30px 基础大小
ctx.save();
ctx.globalAlpha = 0.6 + Math.abs(Math.sin(marker.pulse)) * 0.6; // 透明度动态变化
ctx.drawImage(
markerImg,
marker.x - imgSize / 2, // 让图片中心对齐标记点
marker.y - imgSize / 2,
imgSize / scale,
imgSize / scale
);
ctx.restore();
});
ctx.restore();
animationFrameId = requestAnimationFrame(drawImage);
};
// **计算初始缩放和位置**
const initImagePosition = () => {
const { width: boxWidth, height: boxHeight } = containerRef.value.getBoundingClientRect();
// 计算高度撑满时的缩放比例
scale = boxHeight / img.height;
scale = Math.min(maxScale, Math.max(minScale, scale)); // 限制缩放范围
// 居中图片
position.x = (boxWidth - img.width * scale) / 2;
position.y = 0; // 高度撑满,不需要额外调整 Y 轴
};
// **鼠标滚轮缩放**
const handleWheel = (event) => {
event.preventDefault();
const scaleFactor = event.deltaY > 0 ? 0.9 : 1.1;
const newScale = Math.min(maxScale, Math.max(minScale, scale * scaleFactor));
const rect = canvasRef.value.getBoundingClientRect();
const mouseX = (event.clientX - rect.left - position.x) / scale;
const mouseY = (event.clientY - rect.top - position.y) / scale;
position.x -= (mouseX * newScale - mouseX * scale);
position.y -= (mouseY * newScale - mouseY * scale);
scale = newScale;
};
// **鼠标拖拽**
const handleMouseDown = (event) => {
startDrag = { x: event.clientX, y: event.clientY };
};
const handleMouseMove = (event) => {
if (!startDrag) return;
position.x += event.clientX - startDrag.x;
position.y += event.clientY - startDrag.y;
startDrag = { x: event.clientX, y: event.clientY };
};
const handleMouseUp = () => {
startDrag = null;
};
// **WebSocket 接收标记点**
let socket;
const setupWebSocket = () => {
};
const wsData = computed(() =>
inject('wsData'),
console.log('computed--ws', inject('wsData'))
);
const randomlyGenerateMarkerPoints = () => {
markers.value.push({
x: Math.random() * img.width,
y: Math.random() * img.height,
pulse: 0,
})
}
// **组件挂载**
onMounted(() => {
const canvas = canvasRef.value;
ctx = canvas.getContext('2d');
img = new Image();
img.src = imgSrc;
img.onload = () => {
initImagePosition(); // **初始加载时让图片居中 & 高度撑满**
drawImage();
};
canvas.addEventListener('wheel', handleWheel);
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);
// setupWebSocket();
});
// **组件销毁**
onUnmounted(() => {
const canvas = canvasRef.value;
if (canvas) {
canvas.removeEventListener('wheel', handleWheel);
canvas.removeEventListener('mousedown', handleMouseDown);
canvas.removeEventListener('mousemove', handleMouseMove);
canvas.removeEventListener('mouseup', handleMouseUp);
}
if (socket) {
socket.close();
}
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
});
defineExpose({
randomlyGenerateMarkerPoints,
});
</script>
tips:
我的需求是按照websocket推送的坐标去改变标点位置,部分业务代码被删掉请见谅,不影响效果 放心食用。