canva绘制图片,实现图片缩放,拖动,打标点功能

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推送的坐标去改变标点位置,部分业务代码被删掉请见谅,不影响效果 放心食用。

相关推荐
带娃的IT创业者1 小时前
TypeScript + React + Ant Design 前端架构入门:搭建一个 Flask 个人博客前端
前端·react.js·typescript
非凡ghost2 小时前
MPC-BE视频播放器(强大视频播放器) 中文绿色版
前端·windows·音视频·软件需求
Stanford_11062 小时前
React前端框架有哪些?
前端·微信小程序·前端框架·微信公众平台·twitter·微信开放平台
洛可可白2 小时前
把 Vue2 项目“黑盒”嵌进 Vue3:qiankun 微前端实战笔记
前端·vue.js·笔记
学习同学3 小时前
从0到1制作一个go语言游戏服务器(二)web服务搭建
服务器·前端·golang
-D调定义之崽崽3 小时前
【初学】调试 MCP Server
前端·mcp
四月_h4 小时前
vue2动态实现多Y轴echarts图表,及节点点击事件
前端·javascript·vue.js·echarts
文心快码BaiduComate4 小时前
用Zulu轻松搭建国庆旅行4行诗网站
前端·javascript·后端
正义的大古5 小时前
OpenLayers地图交互 -- 章节十八:拖拽旋转和缩放交互详解
javascript·vue.js·openlayers
行者..................5 小时前
手动编译 OpenCV 4.1.0 源码,生成 ARM64 动态库 (.so),然后在 Petalinux 中打包使用。
前端·webpack·node.js