前端开发攻略---Vue实现图像裁剪功能,支持用户通过图形界面进行裁剪区域的调整,最终生成裁剪后的图像。

news2024/9/22 6:41:54

目录

1、演示

2、实现原理

3、实现功能

4、代码


1、演示

2、实现原理

这里有详细介绍: 前端开发攻略---图片裁剪上传的原理-CSDN博客

3、实现功能

  • 上传图像

    • 用户选择文件后,changeFile 方法读取文件内容并将其转换为 Data URL,这样可以在页面上显示图像。
  • 裁剪区域显示与交互

    • 使用 cutContainerStyle 计算并设置裁剪区域的样式(位置和尺寸)。
    • 通过 handleMouseDownhandleMouseMovehandleMouseUp 方法处理用户对裁剪区域的拖动操作。
    • elMapFn 对象包含处理不同裁剪区域调整(例如四角、边界点)的具体逻辑。
    • @mousemove@wheel 事件处理函数允许用户在调整裁剪区域的同时进行拖动和缩放操作。
  • 裁剪图像

    • cutImageBtn 方法通过以下步骤裁剪图像:
      1. 获取裁剪参数:获取裁剪区域的位置信息和尺寸。
      2. 计算缩放比例:根据图像的显示尺寸和原始尺寸计算缩放比例。
      3. 裁剪图像:创建一个 canvas 元素,使用 drawImage 方法在 canvas 上绘制裁剪区域的图像部分。
      4. 生成裁剪后的图像:将 canvas 内容转换为 Blob 对象,再通过 FileReader 转换为 Data URL,显示在页面上。

4、代码

本案例采用Vue3框架实现。可以直接复制全部代码,放到一个干净的vue文件中即可

<template>
  <div class="app" @mouseup="handleMouseUp">
    <div>
      <input type="file" @change="changeFile" /> <br />
      <br />
      <button @click="cutImageBtn" v-if="originImage">确认裁剪</button>
      <br />
      <img :src="cutAfterUrl" alt="" />
    </div>
    <div class="previewContainer" ref="previewContainer" @mousemove="handleMouseMove" v-if="originImage" @wheel="handleWheel">
      <img :src="originImage" alt="" ref="originImageEl" />
      <div
        class="cutContainer"
        @mousedown="handleMouseDown"
        action="cutContainer"
        ref="cutContainer"
        :style="{
          transform: `translateX(${cutContainerStyle.translateX}px) translateY(${cutContainerStyle.translateY}px)`,
          width: `${cutContainerStyle.width}px`,
          height: `${cutContainerStyle.height}px`,
        }"
      >
        <img
          :src="originImage"
          alt=""
          :style="{
            transform: `translateX(-${cutContainerStyle.translateX}px) translateY(-${cutContainerStyle.translateY}px)`,
          }"
        />
        <span class="point lt" action="lt"></span>
        <span class="point rt" action="rt"></span>
        <span class="point lb" action="lb"></span>
        <span class="point rb" action="rb"></span>
        <span class="point tm" action="tm"></span>
        <span class="point rm" action="rm"></span>
        <span class="point bm" action="bm"></span>
        <span class="point lm" action="lm"></span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
const originImage = ref('')
const originImageEl = ref(null)
const cutContainer = ref(null)
const cutAfterUrl = ref('')
const previewContainer = ref(null)
const cutContainerStyle = reactive({
  translateX: 50,
  translateY: 50,
  width: 400,
  height: 400,
})

const handleMouseUp = () => {
  elMapFn.isMove = false
}
const handleMouseDown = e => {
  e.preventDefault()
  elMapFn.isMove = true
  elMapFn.clickX = e.offsetX
  elMapFn.clickY = e.offsetY
  elMapFn.clickTarget = e.target.getAttribute('action')
}

const elMapFn = {
  isMove: false,
  clickX: 0,
  clickY: 0,
  clickTarget: null,
  cutContainer: function (e) {
    const { left, top, width, height } = previewContainer.value.getBoundingClientRect()
    let x = e.clientX - left - this.clickX
    let y = e.clientY - top - this.clickY
    if (y <= 0) {
      y = 0
    }
    if (x <= 0) {
      x = 0
    }
    if (x >= width - cutContainer.value.offsetWidth) {
      x = width - cutContainer.value.offsetWidth
    }
    if (y >= height - cutContainer.value.offsetHeight) {
      y = height - cutContainer.value.offsetHeight
    }
    cutContainerStyle.translateX = x
    cutContainerStyle.translateY = y
  },
  lt: function (e) {
    const { left, top, width, height } = previewContainer.value.getBoundingClientRect()
    let y = e.clientY - top - this.clickY
    let h = height - y - (height - cutContainerStyle.translateY - cutContainerStyle.height)
    let x = e.clientX - left
    let w = width - x - (width - cutContainerStyle.translateX - cutContainerStyle.width)
    cutContainerStyle.translateX = x
    cutContainerStyle.width = w
    cutContainerStyle.translateY = y
    cutContainerStyle.height = h
  },
  rt: function (e) {
    const { left, top, height } = previewContainer.value.getBoundingClientRect()
    let w = e.clientX - left - cutContainerStyle.translateX
    let y = e.clientY - top - this.clickY
    let h = height - y - (height - cutContainerStyle.translateY - cutContainerStyle.height)
    cutContainerStyle.translateY = y
    cutContainerStyle.width = w
    cutContainerStyle.height = h
  },
  lb: function (e) {
    const { left, top, width } = previewContainer.value.getBoundingClientRect()
    let x = e.clientX - left
    let w = width - x - (width - cutContainerStyle.translateX - cutContainerStyle.width)
    let h = e.clientY - top - cutContainerStyle.translateY
    cutContainerStyle.translateX = x
    cutContainerStyle.width = w
    cutContainerStyle.height = h
  },
  rb: function (e) {
    const { left, top } = previewContainer.value.getBoundingClientRect()
    let w = e.clientX - left - cutContainerStyle.translateX
    let h = e.clientY - top - cutContainerStyle.translateY
    cutContainerStyle.width = w
    cutContainerStyle.height = h
  },
  tm: function (e) {
    const { top, height } = previewContainer.value.getBoundingClientRect()
    let y = e.clientY - top - this.clickY
    let h = height - y - (height - cutContainerStyle.translateY - cutContainerStyle.height)
    cutContainerStyle.translateY = y
    cutContainerStyle.height = h
  },
  rm: function (e) {
    const { left } = previewContainer.value.getBoundingClientRect()
    let w = e.clientX - left - cutContainerStyle.translateX
    cutContainerStyle.width = w
  },
  bm: function (e) {
    const { top } = previewContainer.value.getBoundingClientRect()
    let h = e.clientY - top - cutContainerStyle.translateY
    cutContainerStyle.height = h
  },
  lm: function (e) {
    const { left, top, height, width } = previewContainer.value.getBoundingClientRect()
    let x = e.clientX - left
    let w = width - x - (width - cutContainerStyle.translateX - cutContainerStyle.width)
    cutContainerStyle.translateX = x
    cutContainerStyle.width = w
  },
}
const handleMouseMove = e => {
  if (!elMapFn.isMove || !elMapFn.clickTarget) return
  elMapFn[elMapFn.clickTarget] && elMapFn[elMapFn.clickTarget](e)
}
const changeFile = e => {
  const file = e.target.files[0]
  const fileReader = new FileReader()
  fileReader.onload = e => {
    originImage.value = e.target.result
  }
  fileReader.readAsDataURL(file)
}
const cutImageBtn = () => {
  const { translateX, translateY, width, height } = cutContainerStyle
  // 获取用户输入的裁剪位置
  const x = parseInt(translateX, 10)
  const y = parseInt(translateY, 10)
  const w = parseInt(width, 10)
  const h = parseInt(height, 10)
  // 获取图片的显示尺寸
  const displayWidth = originImageEl.value.clientWidth
  const displayHeight = originImageEl.value.clientHeight
  // 获取图片的原始尺寸
  const originalWidth = originImageEl.value.naturalWidth
  const originalHeight = originImageEl.value.naturalHeight
  // 计算缩放比例
  const xScale = originalWidth / displayWidth
  const yScale = originalHeight / displayHeight
  // 将裁剪位置转换为原始图像上的坐标
  const cropX = x * xScale
  const cropY = y * yScale
  const cropW = w * xScale
  const cropH = h * yScale
  // 创建一个 canvas 元素来进行裁剪
  const cvs = document.createElement('canvas')
  const ctx = cvs.getContext('2d')
  // 设置 canvas 大小与图像相同
  cvs.width = originalWidth
  cvs.height = originalHeight
  // 将图像绘制到 canvas 上
  ctx.drawImage(originImageEl.value, cropX, cropY, cropW, cropH, 0, 0, cvs.width, cvs.height)
  cvs.toBlob(blob => {
    // 拿到file对象 可以将裁剪好后的图片上传服务器
    const file = new File([blob], 'cut-png', { type: 'image/png' })
    const fileReader = new FileReader()
    fileReader.onload = e => {
      cutAfterUrl.value = e.target.result
    }
    fileReader.readAsDataURL(file)
  })
}
</script>

<style scoped>
.app {
  width: 100vw;
  height: 100vh;
  background-color: saddlebrown;
  display: flex;
  justify-content: center;
  align-items: center;
}
#cvs {
  width: 500px;
  height: 500px;
  border: 1px solid red;
}
.previewContainer {
  width: 500px;
  height: 500px;
  background-color: #000;
  box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 24px;
  overflow: hidden;
  position: relative;
  user-select: none;
}
.previewContainer > img {
  position: absolute;
  width: 500px;
  height: 500px;
  opacity: 0.5;
}
.previewContainer .cutContainer {
  width: 400px;
  height: 400px;
  border: 1px solid #266fff;
  overflow: hidden;
  position: relative;
}
.cutContainer:hover {
  cursor: move;
}
.cutContainer > img {
  position: absolute;
  width: 500px;
  height: 500px;
  z-index: -100;
}
.cutContainer::before {
  content: '';
  position: absolute;
  width: calc(100% / 3);
  height: 100%;
  border-left: 1px dashed #eee;
  border-right: 1px dashed #eee;
  left: 50%;
  margin-left: calc(100% / -3 / 2);
}

.cutContainer::after {
  content: '';
  position: absolute;
  width: 100%;
  height: calc(100% / 3);
  border-top: 1px dashed #eee;
  border-bottom: 1px dashed #eee;
  top: 50%;
  margin-top: calc(100% / -3 / 2);
  z-index: 999;
}
.point {
  display: inline-block;
  position: absolute;
  width: 5px;
  height: 5px;
  background-color: #266fff;
  z-index: 9999;
}

.point.lt {
  left: 0;
  top: 0;
}

.point.tm {
  left: 50%;
  transform: translateX(-50%);
}
.point.rt {
  right: 0;
}
.point.rm {
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}
.point.rb {
  right: 0;
  bottom: 0;
}
.point.bm {
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
}
.point.lb {
  bottom: 0;
  left: 0;
}
.point.lm {
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}
.point.tm:hover,
.point.bm:hover {
  cursor: s-resize;
}
.point.lm:hover,
.point.rm:hover {
  cursor: w-resize;
}
.point.lt:hover,
.point.rb:hover {
  cursor: se-resize;
}
.point.rt:hover,
.point.lb:hover {
  cursor: ne-resize;
}
</style>

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

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

相关文章

Amesim中动力电池建模方法与原则简介

引言 新能源动力电池一维仿真与三维仿真的主要区别在与&#xff0c;一维仿真中无法在仿真中精准的得到各个点的温度变化&#xff0c;其仅为质量块的平均温度。而在新能源动力电池一维仿真中&#xff0c;旨在对动力电池的策略、充放电时间等进行验证。而无论是策略还是充放电时…

jmreport测试数据库出现 权限不足,此功能需要分配角色 解决方法

目录 前言1. 问题所示2. 原理分析3. 解决方法前言 关于jmreport的补充可看官网:jmreport上线安全配置 1. 问题所示 jmreport测试数据库出现,出现如下所示的问题:权限不足,此功能需要分配角色! 截图如下所示: 2. 原理分析 对于原理分析的Bug,代表当前用户没有足够的…

HDFS的编程

一、HDFS原理 HDFS(Hadoop Distributed File System)是hadoop生态系统的一个重要组成部分,是hadoop中的的存储组件,在整个Hadoop中的地位非同一般,是最基础的一部分,因为它涉及到数据存储,MapReduce等计算模型都要依赖于存储在HDFS中的数据。HDFS是一个分布式文件系统,…

20款必试AI工具:轻松搞定设计到协作

随着人工智能技术的发展&#xff0c;各种AI工具如雨后春笋般涌现&#xff0c;给我们的工作和生活带来了极大便利。 在AI工具的海洋中&#xff0c;哪一款才是你的真命天子&#xff1f; 众所周知&#xff0c;AI工具如雨后春笋般涌现&#xff0c;让人目不暇接。面对琳琅满目的选…

Oracle 字符串转多行(REGEXP_SUBSTR)

方案一&#xff1a; SQL 1.一个数据表(TABLE1_ZK)中存在一个字段(STRS)&#xff08;存储格式是以【,】隔开的字符串&#xff09; 2.现需要将其查分为多行数据&#xff08;每行为其中一个字符串&#xff09; 3.sql SELECT t.id,REGEXP_SUBSTR(t.STRS, [^,], 1, LEVEL) AS ma…

招聘|头部云厂商招 PG 核心骨干 DBA【上海】

我们的招聘专区又回来了&#xff01;&#x1f3c3; Bytebase 作为先进的数据库 DevOps 团队协同工具 &#x1f527;&#xff0c;用户群里汇聚了 &#x1f497; 业界优秀的 DBA&#xff0c;SRE&#xff0c;运维的同学们 &#x1f31f;。 上周用户群里有小伙伴发招聘信息 &…

【观察者模式】设计模式系列: 实现与最佳实践案例分析

文章目录 观察者模式深入解析&#xff1a;在Java中的实现与应用1. 引言1.1 观察者模式简介1.2 模式的重要性及其在现实世界的应用示例1.3 本文的目标和读者定位 2. 观察者模式的基本概念2.1 定义与原理2.2 UML类图和时序图2.3 核心原则2.4 使用场景 3. 观察者模式与其他模式的关…

【数据结构】Java实现链表

目录 链表的概念 链表的实现 链表的功能 框架搭建 功能实现 打印链表 获取数据数量 查询数据 插入数据 头插法 尾插法 指定位置插入 删除数据 删除一个数据 删除多个相同数据 删除链表 完整代码 链表的概念 链表是一种物理存储结构上非连续存储结构&#xff0…

nosql----redis三主三从集群部署

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【uniapp/uview1.x】解决在 u-popup 弹出层中使用 u-calendar 日历组件弹出方向出 bug 的问题

这个方法适用 uview 1.x 版本&#xff1b; 如果这个方法不适用可能是 uview 版本不一样&#xff0c;可以参考&#xff1a;https://github.com/dcloudio/uni-ui/issues/915 试试看 bug 的效果如图所示&#xff1a; 因为我为 popup 设置的方向为 top&#xff1a; <u-popup …

人工智能算法,图像识别技术;基于大语言模型的跨境商品识别与问答系统;图像识别

目录 一 .研究背景 二,大语言模型介绍 三,数据采集与预处理 商品识别算法 四. 跨境商品问答系统设计 五.需要源码联系 一 .研究背景 在当今全球化的背景下&#xff0c;跨境电商行业迅速发展&#xff0c;为消费者提供了更广泛的购物选择和更便利的购物方式。然而&#xf…

OLED屏幕制造工艺流程

OLED屏幕制造工艺流程是一个复杂且精细的过程&#xff0c;涉及多个关键步骤以确保最终的显示效果和性能。以下是OLED屏幕制造工艺流程的主要步骤&#xff1a; 1. 衬底制作与准备 材料选择&#xff1a;OLED器件需要一个透明的导电衬底&#xff0c;通常使用玻璃或塑料材料。 清…

集成RJ45网口网络变压器(网络隔离变压器)是如何影响网通设备的传输速率的。

华强盛电子导读RJ45连接器网口-199中间2643-0038 集成RJ45网口的网络变压器&#xff08;网络隔离变压器&#xff09;通常是指将网络变压器与RJ45连接器直接集成在一起的产品&#xff0c;这样的设计使得变压器可以直接安装在网络电缆的连接点上&#xff0c;而不需要额外的连接器…

【源码+文档+调试讲解】多媒体信息共享平台

摘 要 随着信息时代的来临&#xff0c;过去的武理多媒体信息共享管理方式缺点逐渐暴露&#xff0c;对过去的武理多媒体信息共享管理方式的缺点进行分析&#xff0c;采取计算机方式构建武理多媒体信息共享系统。本文通过阅读相关文献&#xff0c;研究国内外相关技术&#xff0c…

Unity | 游戏开发中的优化思维

目录 ​​​​​​一、优化三板斧&#xff1a; 第1步&#xff1a;定标准 第2步&#xff1a;重数据 第3步&#xff1a;严测试 二、流程和性能的优化 1.定标准&#xff1a; 2.重数据&#xff1a; 三、交互和表现的优化 1.卡顿和延迟 2.手感硬 四、沟通和学习 ​​​​…

C语言深度解析:static与extern关键字全指南

[大师C语言]合集&#xff3b;大师C语言(第一篇)&#xff3d;C语言栈溢出背后的秘密&#xff3b;大师C语言(第二十五篇)&#xff3d;C语言字符串探秘&#xff3b;大师C语言(第二篇)&#xff3d;C语言main函数背后的秘密&#xff3b;大师C语言(第二十六篇)&#xff3d;C语言结构体…

Electron 集成 Express + p-limit + SQlite WAL读写模式解决并发锁库的问题

背景 经过通信层面的优化后&#xff0c;我们不再走 Electron 提供的内置进程间通信 IPC&#xff0c;改为利用 Express 提供的 Http 本地服务来进行多处直达通信机制&#xff0c;同时利用本地 Sqlite 来保存大量数据&#xff0c;但 Express 提供的本地服务是支持并发请求的&…

食品零食小吃商城管理系统-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设残哥 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目、 源…

DataX二次开发之达梦数据库插件

达梦数据库自定义插件 达梦8的依赖引入定义reader module定义writer module修改核心配置数据库类型支持打包插件测试 以mysql到dm数据库为例配置mysql2dm.json执行任务查询下结果 DataX二次开发之达梦数据库插件 DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,支持…

eNSP 华为远程登录路由器

华为远程登录路由器 前提&#xff1a;主机能与路由器通信就行&#xff0c;如果不同网段就配路由协议&#xff0c;这里直接模拟直连通信 Cloud&#xff1a; R&#xff1a; <Huawei>sys [Huawei]sys R [R]int g0/0/0 [R-GigabitEthernet0/0/0] [R-GigabitEthernet0/0/0]i…