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

相关推荐
糯糯机器10 分钟前
天啦噜,Vue生命周期加上async并不会阻塞子组件的加载
vue.js
ONE_Gua13 分钟前
魔改chromium——源码拉取及编译
前端·后端·爬虫
Codelinghu36 分钟前
做后端的我在公司造了一个前端轮子,领导:嘿!你他娘的真是个天才。
前端
小old弟41 分钟前
vue3模板中ref的实现原理
前端·vue.js
招风的黑耳1 小时前
ElementUI元件库——提升Axure原型设计效率与质量
前端·elementui·axure
Captaincc1 小时前
用MCP 让Claude控制ChatGPT 4o,自动生成吉卜力风格的分镜
前端·claude·mcp
阿白的白日梦1 小时前
JSX 元素隐式具有类型 "any",因为不存在接口 "JSX.IntrinsicElements"。ts 无法使用 JSX,除非提供了 "--js
前端·javascript·typescript
amagi6001 小时前
关于黑马程序员微信小程序案例3-3的静态配置问题
前端
curdcv_po1 小时前
React 进阶:useReducer 详解与实战指南
前端
用户4099322502121 小时前
深入掌握FastAPI与OpenAPI规范的高级适配技巧
前端·javascript·后端