如何使用vue插件Konva实现图片的缩放

前提问题:要完成数据标注的第一步,使用vue插件Konva实现图片的缩放

解决过程:首先安装插件Konva,再进行初始化,嵌入图片,Konva官网地址

解决结果:

1.安装Konva

npm install vue-konva konva --save

2. 创建区域

<template>
  <div
    id="canvas_container"
    class="container"
    @wheel="wheelForScale($event)" 
    ref ='sybs'
  ></div>
</template>

<script setup>
import { onMounted, onBeforeUnmount, reactive,ref ,nextTick} from 'vue'
import Konva from 'konva'
const state = reactive({
  // 舞台对象
  stage: null,
  // 图片加载时就放大缩小的倍数,只是记录一下,后续不再更改此值
  scaleX: 0,
  scaleY: 0,
  // 图片加载后,鼠标滚轮控制的放大缩小倍数
  scaleByX: 1,
  scaleByY: 1,
})
</script>

3.创建舞台 new Konva.Stage()

//注:初始化 Konva 时需要调用 Konva.Stage 构造函数
onMounted(() => {
    state.stage = new Konva.Stage({
        container: 'canvas_container',  //id
        width: sybs.value.clientWidth,
        height: sybs.value.clientHeight,
    });
    window.addEventListener('resize', getWidth);
})

4. 创建背景区域

注:添加了 layer,Dom才会创建对应的layre canvas元素
onMounted(() => {
    var layer = new Konva.Layer();
    // add the layer to the stage
    state.stage.add(layer);
})

5.绘制图片+可拖拽框

// 拖拽时的虚线框
    let targetRect = new Konva.Rect({
      x: 0,
      y: 0,
      width: 0,
      height: 0,
      stroke: 'red',
      strokeWidth: 1 ,
      dash: [20,10]
    })
    targetRect.hide()
    layer.add(targetRect)

    const originImg = new Image()
    originImg.src = "http://39.174.88.209:10791/aihang/img/2023-08-18/MISSION/PICTURE/8039_589763/2/2_ZOOM.jpg"

    originImg.onload = () => {
      // 绘制图片
      let imgWidth = originImg.width
      _imgWidth = imgWidth
      let imgHeight = originImg.height
      _imgHeight = imgHeight
      state.scaleX = state.stage.width() / imgWidth
      state.scaleY = state.stage.height() / imgHeight
      let backRect = new Konva.Rect({
        width: state.stage.width(),
        height: state.stage.height(),
        fillPatternImage: originImg,
        fillPatternScaleX: state.scaleX,
        fillPatternScaleY: state.scaleY
      })
      _backRect = backRect
      layer.add(backRect)
      backRect.zIndex(0)
      layer.draw()
}

6.放大或缩小图标方法

const wheelForScale =  e => {
  // 获取节点数据,等下用来获取鼠标的位置
  const stage = state.stage
  // 设置缩放比例,这个可以自己调,不过我觉得1.2倍挺合适的
  const scaleBy = 1.2
  // 判断鼠标滚轮的滚动方向
  // 修改config中scale的缩放比例,用上一次的值乘以倍数就是放大,除以倍数就是缩小
  if (e.deltaY < 0) {
    state.scaleByX = state.scaleByX * scaleBy
    state.scaleByY = state.scaleByY * scaleBy
  } else {
    state.scaleByX = state.scaleByX / scaleBy
    state.scaleByY = state.scaleByY / scaleBy
  }
  if(state.scaleByX < 1 || state.scaleByY < 1){
    state.scaleByX = 1
    state.scaleByY = 1
    stage.offsetX(0)
    stage.offsetY(0)
    return
  }
  // 获取鼠标位置
  const pointer = stage.getPointerPosition()
  // 这里用鼠标位置乘以放大的比例,得到放大后该点的位置,在减去放大前点所在的位置,得到偏移距离
  const mousePointTo = {
    x: pointer.x  * (state.scaleByX - 1),
    y: pointer.y  * (state.scaleByY - 1)
  }
  // 放大
  stage.scaleX(state.scaleByX)
  stage.scaleY(state.scaleByY)
  // 下面的这两句不加的话,不管鼠标放哪,都会以左上角为中心放大缩小,也就是x0,y0
  // 用偏移距离除以放大倍数得到真正的偏移距离,这样缩放的时候就相当于一直在移动图片,实现根据鼠标位置来放大缩小图片
  stage.offsetX(mousePointTo.x/state.scaleByX)
  stage.offsetY(mousePointTo.y/state.scaleByY)
}

7.标注时鼠标的动作事件

state.stage.on("mousedown", (ev) => {
      const pointer = state.stage.getPointerPosition()
      _mousedownData = pointer
      let rectX = _mousedownData.x / state.scaleByX + state.stage.offsetX()
      let rectY = _mousedownData.y / state.scaleByY + state.stage.offsetY()
      targetRect.x(rectX)
      targetRect.y(rectY)
      targetRect.show()
    })
    state.stage.on("mousemove",(ev) =>{
      if(_mousedownData){
        let pointer = state.stage.getPointerPosition()
        let rectX = _mousedownData.x / state.scaleByX + state.stage.offsetX()
        let rectY = _mousedownData.y / state.scaleByY + state.stage.offsetY()
        let rectW = pointer.x / state.scaleByX - rectX + state.stage.offsetX()
        let rectH = pointer.y / state.scaleByY - rectY + state.stage.offsetY()
        targetRect.width(rectW)
        targetRect.height(rectH)
      }
    })
    state.stage.on("mouseup", (ev) => {
      if(!_mousedownData){
        return
      }
      let pointer = state.stage.getPointerPosition()
      targetRect.width(0)
      targetRect.height(0)
      targetRect.hide()
      // 获取的鼠标位置是不加偏移的
      let rectX = _mousedownData.x / state.scaleByX + state.stage.offsetX()
      let rectY = _mousedownData.y / state.scaleByY + state.stage.offsetY()
      
      let rectW = pointer.x / state.scaleByX - rectX + state.stage.offsetX()
      let rectH = pointer.y / state.scaleByY - rectY + state.stage.offsetY()

      let finalRect = new Konva.Rect({
        x: rectX,
        y: rectY,
        width: rectW,
        height: rectH,
        stroke: 'red',
        strokeWidth: 1,
      })
      layer.add(finalRect)
      layer.draw()
      _mousedownData = null
    })

全部代码

<template><divid="canvas_container"class="container"@wheel="wheelForScale($event)" ref ='sybs'></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, reactive,ref ,nextTick} from 'vue'
import Konva from 'konva'
const state = reactive({// 舞台对象stage: null,// 图片加载时就放大缩小的倍数,只是记录一下,后续不再更改此值scaleX: 0,scaleY: 0,// 图片加载后,鼠标滚轮控制的放大缩小倍数scaleByX: 1,scaleByY: 1,
})
const wheelForScale =  e => {// 获取节点数据,等下用来获取鼠标的位置const stage = state.stage// 设置缩放比例,这个可以自己调,不过我觉得1.2倍挺合适的const scaleBy = 1.2// 判断鼠标滚轮的滚动方向// 修改config中scale的缩放比例,用上一次的值乘以倍数就是放大,除以倍数就是缩小if (e.deltaY < 0) {state.scaleByX = state.scaleByX * scaleBystate.scaleByY = state.scaleByY * scaleBy} else {state.scaleByX = state.scaleByX / scaleBystate.scaleByY = state.scaleByY / scaleBy}if(state.scaleByX < 1 || state.scaleByY < 1){state.scaleByX = 1state.scaleByY = 1stage.offsetX(0)stage.offsetY(0)return}// 获取鼠标位置const pointer = stage.getPointerPosition()// 这里用鼠标位置乘以放大的比例,得到放大后该点的位置,在减去放大前点所在的位置,得到偏移距离const mousePointTo = {x: pointer.x  * (state.scaleByX - 1),y: pointer.y  * (state.scaleByY - 1)}// 放大stage.scaleX(state.scaleByX)stage.scaleY(state.scaleByY)// 下面的这两句不加的话,不管鼠标放哪,都会以左上角为中心放大缩小,也就是x0,y0// 用偏移距离除以放大倍数得到真正的偏移距离,这样缩放的时候就相当于一直在移动图片,实现根据鼠标位置来放大缩小图片stage.offsetX(mousePointTo.x/state.scaleByX)stage.offsetY(mousePointTo.y/state.scaleByY)
}
var _mousedownData = null
var _imgWidth = 0
var _imgHeight = 0
var _backRect = null
const sybs = ref(null);
const getWidth = () => {nextTick(()=>{if(sybs.value){let newwidth = sybs.value.clientWidthlet newheight = sybs.value.clientHeightstate.stage.width(newwidth)state.stage.height(newheight)_backRect.width(state.stage.width())_backRect.height(state.stage.height())_backRect.fillPatternScaleX(state.stage.width()/_imgWidth)_backRect.fillPatternScaleY(state.stage.height()/_imgHeight)}})
};
onMounted(() => {state.stage = new Konva.Stage({container: 'canvas_container',   // id of container <div>width: sybs.value.clientWidth,height: sybs.value.clientHeight,});window.addEventListener('resize', getWidth);// then create layervar layer = new Konva.Layer();// add the layer to the stagestate.stage.add(layer);// 拖拽时的虚线框let targetRect = new Konva.Rect({x: 0,y: 0,width: 0,height: 0,stroke: 'red',strokeWidth: 1 ,dash: [20,10]})targetRect.hide()layer.add(targetRect)const originImg = new Image()originImg.src = "http://39.174.88.209:10791/aihang/img/2023-08-18/MISSION/PICTURE/8039_589763/2/2_ZOOM.jpg"originImg.onload = () => {// 绘制图片let imgWidth = originImg.width_imgWidth = imgWidthlet imgHeight = originImg.height_imgHeight = imgHeightstate.scaleX = state.stage.width() / imgWidthstate.scaleY = state.stage.height() / imgHeightlet backRect = new Konva.Rect({width: state.stage.width(),height: state.stage.height(),fillPatternImage: originImg,fillPatternScaleX: state.scaleX,fillPatternScaleY: state.scaleY})_backRect = backRectlayer.add(backRect)backRect.zIndex(0)layer.draw()}state.stage.on("mousedown", (ev) => {const pointer = state.stage.getPointerPosition()_mousedownData = pointerlet rectX = _mousedownData.x / state.scaleByX + state.stage.offsetX()let rectY = _mousedownData.y / state.scaleByY + state.stage.offsetY()targetRect.x(rectX)targetRect.y(rectY)targetRect.show()})state.stage.on("mousemove",(ev) =>{if(_mousedownData){let pointer = state.stage.getPointerPosition()let rectX = _mousedownData.x / state.scaleByX + state.stage.offsetX()let rectY = _mousedownData.y / state.scaleByY + state.stage.offsetY()let rectW = pointer.x / state.scaleByX - rectX + state.stage.offsetX()let rectH = pointer.y / state.scaleByY - rectY + state.stage.offsetY()targetRect.width(rectW)targetRect.height(rectH)}})state.stage.on("mouseup", (ev) => {if(!_mousedownData){return}let pointer = state.stage.getPointerPosition()targetRect.width(0)targetRect.height(0)targetRect.hide()// 获取的鼠标位置是不加偏移的let rectX = _mousedownData.x / state.scaleByX + state.stage.offsetX()let rectY = _mousedownData.y / state.scaleByY + state.stage.offsetY()let rectW = pointer.x / state.scaleByX - rectX + state.stage.offsetX()let rectH = pointer.y / state.scaleByY - rectY + state.stage.offsetY()let finalRect = new Konva.Rect({x: rectX,y: rectY,width: rectW,height: rectH,stroke: 'red',strokeWidth: 1,})layer.add(finalRect)layer.draw()_mousedownData = null})
})
onBeforeUnmount(() => {window.removeEventListener('resize', getWidth);
})
</script>
<style lang="scss" scoped>
.container {width: 100%;height: 100%;
}
</style>

借鉴了很多博主的思路,有需要可以看一下其他博主的文章~直通车1  直通车2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/758210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

②免费AI软件开发工具测评:通义灵码 VS 码上飞

前言 我又双叒叕来测评了&#xff01;上次给大家带来的是iFlyCode和CodeFlying两款产品的测评&#xff0c;受到了大家的一致好评~ 今天咱就继续来聊聊&#xff0c;这次我们选的的对象是通义灵码和码上飞&#xff0c;从名字上也能看到出来这两款产品一定是跟软件开发有关系的&…

Zerotier 异地组网方案初探

前言 我之前想要异地组网的话&#xff0c;一般都采用内网穿透的方法&#xff0c;但是这个内网穿透有弊端就是都是要通过公网服务器转发流量&#xff0c;对于大流量的传输就比较不方便&#xff0c;我发现了Zerotier 这个工具非常的好用&#xff0c;是基于p2p的 这是一个类似于…

C++初阶:初识模板

在之前的C与C编程中&#xff0c;针对实现同样类型功能的函数我们学会使用了函数重载&#xff0c;终于可以不用记忆多个功能相同但是函数名不同的函数了喵。但是在实现的时候仍然显得有点不太方便&#xff0c;有些冗余。世界是懒人的世界&#xff0c;为了方便懒人的使用&#xf…

YOLOv8 服务器与本地tensorboard映射

TensorBoard: Start with ‘tensorboard --logdir runs/detect/train12’, view at http://localhost:6006/ 服务器端输入后本地一直打不开&#xff0c;无法访问。 解决方法&#xff1a;对服务器6006端口与本地端口进行映射&#xff0c;从而完成本地查看TensorBoard 本地cmd输…

多个OncePerRequestFilter过滤器实现的使用及顺序

多个OncePerRequestFilter过滤器实现的使用及顺序 作用 在一次外部请求中只过滤一次, 对于服务器内部之间的forward等请求&#xff0c;不会再次执行过滤方法可以定义多个实现类, 各自处理各自的过滤工作, 并指定过滤顺序 使用场景 token校验有效性Xss处理敏感词汇过滤 … …

驶向高效巅峰:Nginx高并发性能优化实战指南与场景案例拆解

身处瞬息万变的互联网世界&#xff0c;高并发场景下服务器的性能优化尤为重要&#xff0c;而Nginx正是这一领域的超级舵手。本文将深入浅出地揭示Nginx在高并发环境下的性能优化之道&#xff0c;并通过具体场景配置案例&#xff0c;助你掌握这一关键技术&#xff0c;提升服务器…

静态网络配置

一、查看网络命令 1.命令行查看网络配置 1、查看ip\硬件设备-网卡 ifconfig -a ifconfig ens160 网卡名称 ip addr show ip addr show ens160 nmcli device show ens160 nmcli con up ens160 2、主机名称 hostname hostname hfj.huaxia.com 3、查看路由和网关 rou…

慎投!共10本「On Hold」期刊被剔除,2本中科院TOP仍在调查中!

2024年3月18日&#xff0c;科睿唯安本年度第三次更新了Web of Science核心期刊目录。 此次SCIE/SSCI期刊目录共8本期刊发生变动&#xff0c;情况如下&#xff1a; 经小编查询&#xff0c;此次更新后&#xff0c;共有7本“On Hold”期刊被数据库剔除&#xff0c;其中还包括2024年…

Winsock编程入门和枚举协议

Winsock的初始化和清除代码类似如下; if ((ret = WSAStartup(MAKEWORD(2,2), &wsadata)) != 0) { wsprintf(buf,TEXT("winsock初始化失败,错误:%d"), ret); ...... return 0; } 。。。。。。 if (WSACleanup() == SOCKET_ERRO…

手撕算法-最长公共子序列(二)

最长公共子序列(二) 分析&#xff1a;典型的动态规划&#xff0c;直接看代码了。 代码&#xff1a; import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** longest common sub…

VS+QT Debug正常但Release无法识别头文件

&#xff01;&#xff01;&#xff01;&#xff0c;这个问题一般是在第一次编译的时候遇见的&#xff0c;包括之前使用debug也是 在Qt Installation一定要修改成自己版本的编译器&#xff0c;修改一次以后基本是不用再修改的

【机器学习】TinyML的介绍以及在运动健康领域的应用

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

将图像根据注释文件划分

import os import json import shutil# 完整图像文件夹路径 img_dir data\sodaa\datasets# 注释文件夹路径 train_ann_dir data\sodaa\Annotations\\train val_ann_dir data\sodaa\Annotations\\val test_ann_dir data\sodaa\Annotations\\test# 输出文件夹路径 output_dir…

5465: 【搜索】奶牛干饭

题目描述 农场主John把农场分为了一个 r 行 c 列的矩阵&#xff0c;并发现奶牛们无法通过其中一些区域。此刻&#xff0c;Bessie 位于坐标为 (1,1) 的区域&#xff0c;并想到坐标为 (r,c) 的牛棚享用晚餐。她知道&#xff0c;以她所在的区域为起点&#xff0c;每次移动至相邻的…

全球盲盒火热下,海外盲盒APP助力我国盲盒出海

盲盒具有不确定性&#xff0c;与各类热门影视动漫合作推出的专属盲盒商品&#xff0c;吸引了无数年轻人&#xff0c;成为了年轻人的娱乐消费首选方式。 在互联网电商的推动下&#xff0c;盲盒在全球内的市场规模迅速扩大。受到市场增长的影响&#xff0c;各类资本公司也纷纷进…

【Python多线程】的进阶讲解

Python多线程 1. 前言2. threading 模块的基本用法3. Thread类4. 锁&#xff08;Locks&#xff09;5. 守护线程&#xff08;Daemon Threads&#xff09;6. 运用场景7. 弊端 1. 前言 Python中的多线程通过threading模块来实现&#xff0c;它允许你并发执行多个线程&#xff0c;…

深入浅出前端本地储存(1)

引言 2021 年&#xff0c;如果你的前端应用&#xff0c;需要在浏览器上保存数据&#xff0c;有三个主流方案&#xff1a; CookieWeb Storage (LocalStorage)IndexedDB 这些方案就是如今应用最广、浏览器兼容性最高的三种前端储存方案 今天这篇文章就聊一聊这三种方案的历史…

基于python的4s店客户管理系统

技术&#xff1a;pythonmysqlvue 一、背景 进入21世纪网络和计算机得到了飞速发展&#xff0c;并和生活进行了紧密的结合。目前&#xff0c;网络的运行速度以达到了千兆&#xff0c;覆盖范围更是深入到生活中的角角落落。这就促使管理系统的发展。网上办公可以实现远程处理事务…

pyvista可视化代码优化

同时显示多组点云 import os import glob import randomimport pyvista as pvdef display_multi_mesh(meshes: list, titlesNone, point_size3, opacity0.9):num len(meshes)pl pv.Plotter(shape(1, num))pl.set_background([0.9, 0.9, 0.9])for i in range(num):pl.subplo…

jmeter打开文件报异常无法打开

1、问题现象&#xff1a; 报错部分内容&#xff1a; java.desktop does not export sun.awt.shell to unnamed module 0x78047b92 [in thread "AWT-EventQueue-0"] 报错部分内容&#xff1a; kg.apc.jmeter.reporters.LoadosophiaUploaderGui java.lang.reflect.Invo…