cropperjs 裁剪/框选图片

1.效果

在这里插入图片描述

2.使用组件

<!-- 父级 -->
 <Cropper ref="cropperRef" :imgUrl="url" @searchImg="searchImg"></Cropper>

3.封装组件

<template>
  <el-dialog :title="title" :visible.sync="dialogVisible" width="1000px">
    <input ref="input" type="file" name="image" @change="setImage" />
    <div class="flex justify-around">
      <div class="w-480px h-270px flex justify-center items-center">
        <div
          v-show="!imgSrc"
          @click="showFileChooser"
          class="w-full h-full flex cursor-pointer justify-center items-center border-1px border-dashed border-gray-300 rounded-lg"
        >
          <i class="font-size-20px el-icon-plus avatar-uploader-icon"></i>
        </div>
        <!-- :aspect-ratio="16 / 16" -->
        <vue-cropper
          v-show="imgSrc"
          class="w-full h-full"
          ref="cropper"
          :src="imgSrc"
          alt="Source Image"
          @ready="ready"
          @cropstart="cropstart"
          @cropmove="cropmove"
          @cropend="cropend"
          @crop="crop"
          @zoom="zoom"
          preview=".preview"
          :autoCropArea="autoCropArea"
        >
        </vue-cropper>
      </div>
      <div class="w-420px">
        <div class="font-bold color-#666 ml-20px mb-10px">预览</div>
        <div v-show="!imgSrc" class="preview_empty ml-20px"></div>
        <div v-show="imgSrc" class="preview ml-20px"></div>
        <!-- <div>裁剪图片</div>
        <div class="cropped-image">
          <el-image class="h-180px" v-if="cropImg" :src="cropImg" alt="Cropped Image" />
          <div v-else class="crop-placeholder" />
        </div> -->
        <div class="actions mt-10px ml-10px">
          <el-button class="mb-10px ml-10px" type="primary" @click="zoom(0.2)" size="small">放大</el-button>
          <el-button class="mb-10px" type="primary" @click="zoom(-0.2)" size="small">缩小</el-button>
          <el-button class="mb-10px" type="primary" @click="move(-10, 0)" size="small">左移</el-button>
          <el-button class="mb-10px" type="primary" @click="move(10, 0)" size="small">右移</el-button>
          <el-button class="mb-10px" type="primary" @click="move(0, -10)" size="small">上移</el-button>
          <el-button class="mb-10px" type="primary" @click="move(0, 10)" size="small">下移</el-button>
          <el-button class="mb-10px" type="primary" @click="rotate(90)" size="small">旋转90°</el-button>
          <el-button class="mb-10px" type="primary" @click="rotate(-90)" size="small">旋转-90°</el-button>
          <!-- <el-button class="mb-10px" type="primary" @click="flipX" size="small">水平翻转</el-button>
          <el-button class="mb-10px" type="primary" @click="flipY" size="small">垂直翻转</el-button> -->
          <!-- <el-button class="mb-10px" type="success" @click="cropImage" size="small">搜索</el-button> -->
          <el-button class="mb-10px" type="primary" @click="reset" size="small" plain>重置</el-button>
          <el-button
            v-if="!isHideFileChooser"
            class="mb-10px"
            type="success"
            @click="showFileChooser"
            size="small"
            plain
            >更换图片</el-button
          >

          <!-- <el-button class="mb-10px" type="primary" @click="getCropBoxData" size="small">获取裁剪框数据</el-button>
          <el-button class="mb-10px" type="primary" @click="setCropBoxData" size="small">设置裁剪框数据</el-button>
          <el-button class="mb-10px" type="primary" @click="getData" size="small">获取裁剪数据</el-button>
          <el-button class="mb-10px" type="primary" @click="setData" size="small">设置裁剪数据</el-button> -->
        </div>
      </div>
    </div>

    <span slot="footer" class="dialog-footer">
      <el-button size="small" @click="dialogVisible = false">取 消</el-button>
      <el-button size="small" type="primary" @click="cropImage">搜索</el-button>
    </span>
  </el-dialog>
</template>

<script>
import VueCropper from 'vue-cropperjs'
import 'cropperjs/dist/cropper.css'

export default {
  name: 'Cropper',
  components: { VueCropper },
  props: {
    title: {
      type: String,
      default: '图片框选'
    },
    imgUrl: {
      type: String,
      default: ''
    },
    autoCropArea: {
      type: Number,
      default: 0.6
    },
    isHideFileChooser: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      imgSrc: '',
      dialogVisible: false,
      cropImg: ''
    }
  },
  watch: {
    imgUrl(val) {
      if (val) {
        this.imgSrc = val
        console.log('🚀 ~ imgUrl ~ this.imgSrc:', this.imgSrc)
      }
    }
  },
  methods: {
    open() {
      if (!this.imgUrl) {
        this.imgSrc = ''
      }
      this.dialogVisible = true
    },
    handleClose() {
      this.$emit('close')
    },
    ready() {
      // console.log('🚀 ~ ready ~ this.$refs.cropper:', this.$refs.cropper)
    },
    cropImage() {
      // get image data for post processing, e.g. upload or setting image src
      this.cropImg = this.$refs.cropper.getCroppedCanvas().toDataURL()
      const base64Data = this.cropImg.split(',')[1]
      this.$emit('searchImg', base64Data)
      this.dialogVisible = false
    },
    cropstart() {
      // console.log('🚀 ~ cropstart ~')
    },
    cropmove() {
      // console.log('🚀 ~ cropmove ~')
    },
    cropend() {
      // console.log('🚀 ~ cropend ~')
    },
    crop(data) {
      // console.log('🚀 ~ crop ~ data:', data)
    },
    flipX() {
      const dom = this.$refs.flipX
      let scale = dom.getAttribute('data-scale')
      scale = scale ? -scale : -1
      this.$refs.cropper.scaleX(scale)
      dom.setAttribute('data-scale', scale)
    },
    flipY() {
      const dom = this.$refs.flipY
      let scale = dom.getAttribute('data-scale')
      scale = scale ? -scale : -1
      this.$refs.cropper.scaleY(scale)
      dom.setAttribute('data-scale', scale)
    },
    getCropBoxData() {
      this.data = JSON.stringify(this.$refs.cropper.getCropBoxData(), null, 4)
    },
    getData() {
      this.data = JSON.stringify(this.$refs.cropper.getData(), null, 4)
      console.log('🚀 ~ getData ~ this.data:', this.data)
    },
    move(offsetX, offsetY) {
      this.$refs.cropper.move(offsetX, offsetY)
    },
    reset() {
      this.$refs.cropper.reset()
    },
    rotate(deg) {
      this.$refs.cropper.rotate(deg)
    },
    setCropBoxData() {
      if (!this.data) return
      this.$refs.cropper.setCropBoxData(JSON.parse(this.data))
    },
    setData() {
      if (!this.data) return
      this.$refs.cropper.setData(JSON.parse(this.data))
    },
    setImage(e) {
      const file = e.target.files[0]
      if (file.type.indexOf('image/') === -1) {
        alert('Please select an image file')
        return
      }
      if (typeof FileReader === 'function') {
        const reader = new FileReader()
        reader.onload = (event) => {
          this.imgSrc = event.target.result
          // rebuild cropperjs with the updated source
          this.$refs.cropper.replace(event.target.result)
        }
        reader.readAsDataURL(file)
      } else {
        alert('Sorry, FileReader API not supported')
      }
    },
    showFileChooser() {
      this.$refs.input.click()
    },
    zoom(percent) {
      this.$refs.cropper.relativeZoom(percent)
    }
  },
  mounted() {
    this.imgSrc = this.imgUrl
  }
}
</script>

<style lang="scss" scoped>
input[type='file'] {
  display: none;
}

.preview-area {
  width: 100%;
}

.preview-area p {
  font-size: 1.25rem;
  margin: 0;
  margin-bottom: 1rem;
}

.preview-area p:last-of-type {
  margin-top: 1rem;
}

.preview {
  width: 270px;
  height: calc(270px * (9 / 16));
  overflow: hidden;
  background-color: #f5f5f5;
}

.preview_empty {
  width: 270px;
  height: calc(270px * (9 / 16));
  overflow: hidden;
  background-color: #f5f5f5;
}
.crop-placeholder {
  width: 100%;
  height: 200px;
  background: #ccc;
}

.cropped-image img {
  max-width: 100%;
}
</style>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

游戏服务器研究二:大世界的 scale 问题

这是一个非常陈旧的话题了&#xff0c;没什么新鲜的&#xff0c;但本人对 scale 比较感兴趣&#xff0c;所以研究得比较多。 本文不会探讨 MMO 类的网游提升单服承载人数有没有意义&#xff0c;只单纯讨论技术上如何实现。 像 moba、fps、棋牌、体育竞技等 “开房间类型的游戏…

前端:HTML、CSS、JavaScript 代码注释 / 注释与代码规范

一、HTML 行内注释 HTML注释是在HTML代码中添加说明和解释的一种方法&#xff0c;这些注释不会被浏览器渲染或显示在页面上&#xff0c;而是被浏览器忽略。HTML注释对于代码的可读性、可维护性和团队协作非常重要。 1.1、HTML注释的语法 HTML注释的语法是以<!--开始&…

中小学劳技课程开展创意木工 传承非遗木工魅力

学生劳技课程&#xff0c;全称劳动技术课程&#xff0c;是一门旨在通过实践活动培养学生的劳动技能、创新思维、实践能力和社会责任感的基础教育课程。这门课程强调学生的参与和体验&#xff0c;让学生在动手实践中学习并掌握知识&#xff0c;提高解决问题的能力。 学生劳技课程…

大模型应用研发基础环境配置(Miniconda、Python、Jupyter Lab、Ollama等)

老牛同学之前使用的MacBook Pro电脑配置有点旧&#xff08;2015 年生产&#xff09;&#xff0c;跑大模型感觉有点吃力&#xff0c;操作起来有点卡顿&#xff0c;因此不得已捡起了尘封了快两年的MateBook Pro电脑&#xff08;老牛同学其实不太喜欢用 Windows 电脑做研发工作&am…

解码数智升级良方:中国一拖、中原传媒、神火股份等企业数字化实践分析

大模型、AI等技术的成熟以及政策法规的细化&#xff0c;数据资源的权属论证、合规确权、资产论证等环节逐渐走向实用性、价值化。 而伴随着“业财税数融”综合性数字化成为企业数字化转型的主流选择&#xff0c;财务部门的纽带属性被放大&#xff0c;财务数据的融合能力成为企业…

ABC234G Divide a Sequence 题解

题目来源 ABC234G 洛谷 Description 给定长度为 n n n 的序列 { a n } \{a_n\} {an​}。定义一种将 { a n } \{a_n\} {an​} 划分为若干段的方案的价值为每段的最大值减去最小值的差的乘积。求所有划分方案的价值的总和并对 998244353 998244353 998244353 取模。 1 ≤…

Vue3 使用 Vue Router 时,params 传参失效

前言&#xff1a; 在写项目的时候&#xff0c;使用了 vue-router 的 params 进行传参&#xff0c;但是在详情页面中一直获取不到参数。原因&#xff1a;Vue Router 在2022-8-22的那次更新后&#xff0c;使用这种方式在新页面上无法获取&#xff01; 正文&#xff1a; 在列表页进…

从零开始做题:老照片中的密码

老照片中的密码 1.题目 1.1 给出图片如下 1.2 给出如下提示 这张老照片中的人使用的是莫尔斯电报机&#xff0c;莫尔斯电报机分为莫尔斯人工电报机和莫尔斯自动电报机&#xff08;简称莫尔斯快机&#xff09;。莫尔斯人工电报机是一种最简单的电报机&#xff0c;由三个部分组…

【笔记】从零开始做一个精灵龙女-拆uv阶段

目录 先回顾一下拆uv的基础流程吧 肩部盔甲分UV示例 手环UV部分 腰带UV部分 其它也差不多&#xff0c;需要删掉一半的就先提前删掉一半&#xff0c;然后把不需要的被遮挡的面也删掉 龙角UV 胸甲UV 侧边碎发UV 马尾UV 脸部/耳朵UV 特殊情况&#xff1a;如果要删一半再…

kafka的命令行操作

kafka-topics.bat 该命令行和主题相关 kafka启动后&#xff0c;默认端口为9092,可修改 找到kafka_2.13-3.6.2\bin\windows目录下的kafka-topics.bat&#xff0c;用cmd执行 按下会有提示&#xff0c;REQURIED代表为必输项 创建topic 创建一个名为test的topic队列 kafka-t…

绘制图形

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在前3节的实例中&#xff0c;我们一直绘制的都是直线&#xff0c;实际上&#xff0c;海龟绘图还可以绘制其他形状的图形&#xff0c;如圆形、多边形等…

FineReport聚合报表与操作

一、报表类型 模板设计是 FineReport 学习过程中的主要难题所在&#xff0c;FineReport 模板设计主要包括普通报表、聚合报表、决策报表三种设计类型。 报表类型简介- FineReport帮助文档 - 全面的报表使用教程和学习资料 二、聚合报表 2-1 介绍 聚合报表指一个报表中包含多个…

STM32的SPI通信

1 SPI协议简介 SPI&#xff08;Serial Peripheral Interface&#xff09;协议是由摩托罗拉公司提出的通信协议&#xff0c;即串行外围设备接口&#xff0c;是一种高速全双工的通信总线。它被广泛地使用在ADC、LCD等设备与MCU间&#xff0c;使用于对通信速率要求较高的场合。 …

扩散模型 GLIDE:35 亿参数的情况下优于 120 亿参数的 DALL-E 模型

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

LeetCode 算法:二叉树的层序遍历 c++

原题链接&#x1f517;&#xff1a;二叉树的层序遍历 难度&#xff1a;中等⭐️⭐️ 题目 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;roo…

TensorFlow开源项目

欢迎来到 Papicatch的博客 文章目录 &#x1f349;TensorFlow介绍 &#x1f349;主要特点和功能 &#x1f348;多语言支持 &#x1f348;灵活的架构 &#x1f348;分布式训练 &#x1f348;跨平台部署 &#x1f348;强大的工具链 &#x1f348;丰富的社区和生态系统 &a…

人工智能与物联网:融合创新驱动未来

引言 人工智能&#xff08;AI&#xff09;指的是计算机系统模拟人类智能的能力&#xff0c;包括学习、推理、问题解决、理解自然语言以及感知和响应环境的能力。AI技术涵盖了机器学习、深度学习、神经网络、自然语言处理等领域&#xff0c;广泛应用于图像识别、语音识别、自动驾…

FPGA学习笔记(5)——硬件调试与使用内置的集成逻辑分析仪(ILA)IP核

如果要对信号进行分析&#xff0c;可以使用外置的逻辑分析仪&#xff0c;但成本较高&#xff0c;对初学者来说没有必要&#xff0c;可以使用Xilinx Vivado内自带的逻辑分析仪IP核对信号进行分析&#xff0c;不过需要占用一定的芯片资源。 本节采用上一节配置的LED灯闪烁代码&a…

如何改善老年人的行走姿势以减少小碎步现象?

改善老年人行走姿势的方法 为了改善老年人的行走姿势并减少小碎步现象&#xff0c;可以采取以下几种方法&#xff1a; 平衡训练&#xff1a;通过使用单脚站立架、平衡板等器械&#xff0c;提高身体稳定性和协调性&#xff0c;增强核心稳定性及下肢肌肉力量&#xff0c;从而改善…

数据结构-顺序表的交换排序

顺序表的初始化 const int M 505;typedef struct{int key; //关键元素int others; //其他元素 }info;typedef struct{info r[M1]; int length(); //表长 }SeqList,*PSeqList; 冒泡排序 分析&#xff1a; 顺序表的冒泡排序和数组的冒泡排序的…