修正版头像上传组件

news2024/12/23 5:16:24

修正版头像上传组件

    • 文章说明
    • 核心源码展示
    • 运行效果展示
    • 源码下载

文章说明

在头像剪切上传一文中,我采用div做裁剪效果,感觉会有一些小问题,在昨天基于canvas绘制的功能中改进了一版,让代码变得更简洁,而且通用性相对高一些,源码及效果展示如下;包含拖拽和调整裁剪框的效果

核心源码展示

主要包括App.vue中的元素和事件,以及Rectangle.js内的绘图方法和鼠标移动事件

App.vue

<script setup>
import {nextTick, reactive} from "vue";
import {Rectangle} from "@/Rectangle";

const data = reactive({
  selectFile: false,
  imgWidth: 0,
  imgHeight: 0,
});

let image;
let leftCanvas;
let leftContext;
let rightCanvas;
let rightContext;
let rect;
let rectangle;

async function selectFile() {
  const pickerOpts = {
    types: [
      {
        description: "Images",
        accept: {
          "image/*": [".png", ".jpeg", ".jpg"],
        },
      },
    ],
    excludeAcceptAllOption: true,
    multiple: false,
  };
  const fileHandle = await window.showOpenFilePicker(pickerOpts);
  const file = await fileHandle[0].getFile();

  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = function (e) {
    data.src = e.target.result;
    data.selectFile = true;

    image = new Image();
    image.src = e.target.result;

    nextTick(() => {
      data.imgWidth = image.width;
      data.imgHeight = image.height;

      nextTick(() => {
        leftCanvas = document.getElementsByClassName("left-canvas")[0];
        rect = leftCanvas.getBoundingClientRect();

        rightCanvas = document.getElementsByClassName("right-canvas")[0];
        leftContext = leftCanvas.getContext("2d");
        rightContext = rightCanvas.getContext("2d");
        rectangle = new Rectangle(data.imgWidth, data.imgHeight);
        change = true;
        draw();

        leftCanvas.onmousedown = (e) => {
          omMouseDown(e);
        };
        leftCanvas.onmousemove = (e) => {
          changeSize(e);
        };
      });
    });
  };
}

let change;

function draw() {
  if (change) {
    drawLeftImage(image, rectangle);
    drawRightImage(image, rectangle);
    change = false;
  }
  requestAnimationFrame(draw);
}

function drawLeftImage(image, rectangle) {
  leftContext.drawImage(image, 0, 0, data.imgWidth, data.imgHeight, 0, 0, data.imgWidth, data.imgHeight);
  rectangle.draw(leftContext);
}

function drawRightImage(image, rectangle) {
  rightContext.drawImage(image, rectangle.startX, rectangle.startY, rectangle.width, rectangle.height, 0, 0, rightCanvas.width, rightCanvas.height);
}

function omMouseDown(e) {
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;

  const {startX, startY, endX, endY} = rectangle;
  const inGap = rectangle.inGap(clickX, clickY);
  if (inGap > 0) {
    leftCanvas.onmousemove = (e) => {
      rectangle.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, leftCanvas);
      change = true;
    };
  } else {
    leftCanvas.onmousemove = (e) => {
      rectangle.mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, leftCanvas);
      change = true;
    };
  }

  window.onmouseup = () => {
    leftCanvas.onmousemove = null;
    leftCanvas.onmousemove = (e) => {
      changeSize(e);
    };
  };
}

function changeSize(e) {
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;
  const inGap = rectangle.inGap(clickX, clickY);
  const {startX, startY, endX, endY} = rectangle;
  rectangle.mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, leftCanvas);
}
</script>

<template>
  <div class="avatar-container">
    <div class="img-container">
      <div v-show="!data.selectFile" class="select-file" @click="selectFile">
        <p>jpg/png file with a size less than 5MB<em>click to upload</em></p>
      </div>
      <canvas v-show="data.selectFile" :height="data.imgHeight" :width="data.imgWidth" class="left-canvas"></canvas>
    </div>

    <canvas class="right-canvas" height="100" width="100"></canvas>
  </div>
</template>

<style scoped>
.avatar-container {
  margin: 0 auto;
  width: fit-content;
  user-select: none;
  display: flex;
  justify-content: center;
  align-items: center;
  padding-top: 100px;

  .img-container {
    position: relative;
    width: fit-content;

    .select-file {
      width: 500px;
      height: 300px;
      border: 1px dashed #dcdfe6;
      border-radius: 20px;
      display: flex;
      justify-content: center;
      align-items: center;

      &:hover {
        border: 1px dashed #409eff;
        cursor: pointer;
      }

      p {
        font-size: 14px;
        color: #606266;

        em {
          color: #409eff;
          font-style: normal;
          margin-left: 5px;
        }
      }
    }
  }

  .left-canvas {
    margin-left: 30px;
    border: 1px dashed #409eff;
    float: left;
  }

  .right-canvas {
    margin-left: 30px;
    border: 1px dashed #409eff;
    float: left;
  }
}
</style>

Rectangle.js

const gap = 10;
const initValue = 100;

export class Rectangle {
    constructor(imageWidth, imageHeight) {
        const startX = (imageWidth - initValue) / 2;
        const startY = (imageHeight - initValue) / 2;
        this.startX = startX;
        this.startY = startY;
        this.endX = this.startX + initValue;
        this.endY = this.startY + initValue;
        this.imageWidth = imageWidth;
        this.imageHeight = imageHeight;
    }

    get minX() {
        return Math.min(this.startX, this.endX);
    }

    get maxX() {
        return Math.max(this.startX, this.endX);
    }

    get minY() {
        return Math.min(this.startY, this.endY);
    }

    get maxY() {
        return Math.max(this.startY, this.endY);
    }

    get width() {
        return this.maxX - this.minX;
    }

    get height() {
        return this.maxY - this.minY;
    }

    draw(ctx) {
        ctx.beginPath();
        ctx.moveTo(this.minX, this.minY);
        ctx.setLineDash([3, 2]);
        ctx.lineTo(this.maxX, this.minY);
        ctx.lineTo(this.maxX, this.maxY);
        ctx.lineTo(this.minX, this.maxY);
        ctx.lineTo(this.minX, this.minY);
        ctx.strokeStyle = "#409eff";
        ctx.lineWidth = 1;
        ctx.lineCap = "square";
        ctx.stroke();
    }

    // 上1、下2、左4、右8
    // 得到上1、下2、左4、左上5、左下6、右8、右上9、右下10
    inGap(x, y) {
        let result = 0;
        if (Math.abs(this.minY - y) <= gap && (this.minX - gap <= x && this.maxX + gap >= x)) {
            result += 1;
        }
        if (Math.abs(this.maxY - y) <= gap && (this.minX - gap <= x && this.maxX + gap >= x)) {
            result += 2;
        }
        if (Math.abs(this.minX - x) <= gap && (this.minY - gap <= y && this.maxY + gap >= y)) {
            result += 4;
        }
        if (Math.abs(this.maxX - x) <= gap && (this.minY - gap <= y && this.maxY + gap >= y)) {
            result += 8;
        }
        return result;
    }

    mouseMoveChangePos(e, rect, startX, startY, endX, endY, clickX, clickY, canvas) {
        const disX = e.clientX - rect.left - clickX;
        const disY = e.clientY - rect.top - clickY;
        if (startX + disX >= 0) {
            this.startX = startX + disX;
        }
        if (endX + disX <= this.imageWidth) {
            this.endX = endX + disX;
        }
        if (startY + disY >= 0) {
            this.startY = startY + disY;
        }
        if (endY + disY <= this.imageHeight) {
            this.endY = endY + disY;
        }
        canvas.style.cursor = "move";
    }

    mouseMoveChangeSize(e, rect, startX, startY, endX, endY, clickX, clickY, inGap, canvas) {
        const disX = e.clientX - rect.left - clickX;
        const disY = e.clientY - rect.top - clickY;
        if (endX + disX < startX || endY + disY < startY || startX + disX > endX || startY + disY > endY) {
            return;
        }

        switch (inGap) {
            case 1:
                canvas.style.cursor = "n-resize";
                this.startY = startY + disY;
                break;
            case 2:
                canvas.style.cursor = "s-resize";
                this.endY = endY + disY;
                break;
            case 4:
                canvas.style.cursor = "w-resize";
                this.startX = startX + disX;
                break;
            case 5:
                canvas.style.cursor = "nw-resize";
                this.startX = startX + disX;
                this.startY = startY + disY;
                break;
            case 6:
                canvas.style.cursor = "sw-resize";
                this.startX = startX + disX;
                this.endY = endY + disY;
                break;
            case 8:
                canvas.style.cursor = "e-resize";
                this.endX = endX + disX;
                break;
            case 9:
                canvas.style.cursor = "ne-resize";
                this.endX = endX + disX;
                this.startY = startY + disY;
                break;
            case 10:
                canvas.style.cursor = "se-resize";
                this.endX = endX + disX;
                this.endY = endY + disY;
                break;
            default:
                canvas.style.cursor = "default";
                break;
        }
    }
}

运行效果展示

点击选择图片
在这里插入图片描述

可以拖动裁剪框
在这里插入图片描述

可以调整裁剪框大小
在这里插入图片描述

源码下载

头像上传组件

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

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

相关文章

Linux的load(负载)

负载(load)是Linux机器的一个重要指标&#xff0c;直观了反应了机器当前的状态。 在Linux系统中&#xff0c;系统负载是对当前CPU工作量的度量&#xff0c;被定义为特定时间间隔内运行队列中的平均线程数。 Linux的负载高&#xff0c;主要是由于CPU使用、内存使用、10消…

[UTCTF2020]babymips

水一篇 32位 c写的&#xff0c;长得比较丑陋 进入sub-401164函数 V7的数据可以得到 unsigned char ida_chars[] {0x62, 0x6C, 0x7F, 0x76, 0x7A, 0x7B, 0x66, 0x73, 0x76, 0x50, 0x52, 0x7D, 0x40, 0x54, 0x55, 0x79, 0x40, 0x49, 0x47, 0x4D, 0x74, 0x19, 0x7B, 0x6A, 0x…

大数据之路 读书笔记 Day5 数据同步遇到的问题与解决方案

回顾 Day 4 数据同步Day 3 无线客户端的日志采集 1. 分库分表的处理 分库分表&#xff08;Sharding&#xff09;是数据库水平扩展的一种策略&#xff0c;当单个数据库的性能和存储能力无法满足应用需求时&#xff0c;可以采用分库分表来分散数据和查询负载。它通常包括两个方面…

钡铼4G无线RTU助力智慧能源发展实现电网远程调控

随着全球对清洁能源和高效能源管理的需求日益增长&#xff0c;智慧能源技术正逐渐成为推动可持续发展的重要驱动力。在这一背景下&#xff0c;钡铼4G无线远程终端单元正在为智慧能源的发展和电网的远程调控提供强有力的支持。 钡铼4G无线RTU&#xff1a;智慧能源的神经网络 钡…

数据恢复篇:适用于 Android 的恢复工具

正在摆弄 Android 设备。突然&#xff0c;您意外删除了一张或多张图片。不用担心&#xff0c;您总能找到一款价格实惠的照片恢复应用。这款先进的软件可帮助 Android 用户从硬盘、安全数字 (SD) 或存储卡以及数码相机中恢复已删除的图片。 Android 上文件被删除的主要原因 在获…

Blender使用(二)点线面基本操作

Blender使用之点线面 1.编辑模式 tab键进行切换&#xff0c;为了方便菜单调出&#xff0c;可以设置键位映射为拖动时的饼菜单。 设置好后&#xff0c;按住tab键移动鼠标(注意不要点击鼠标)&#xff0c;即可弹出编辑菜单。 默认是点模式&#xff0c;在左上角可进行点线面的切换…

Linux系统OpenSSH出现漏洞(CVE-2024-6387)修复

CVE-2024-6387 是一个影响 OpenSSH 服务器&#xff08;sshd&#xff09;的严重远程代码执行&#xff08;RCE&#xff09;漏洞&#xff0c;它允许未经身份验证的攻击者在受影响的 Linux 系统上以 root 权限执行任意代码。此漏洞尤其危险&#xff0c;因为它可以在不需要任何用户交…

husky 和 lint-staged 构建代码项目规范

目录 前言 最简单的方法 过 scripts 来解决如果检测工具多&#xff0c;需要多次处理 通过 husky(哈士奇)来解决容易遗忘的问题 1. 安装 2. husky init 3. 试一试​ lint-stadge 只 lint 改动的 1. 安装 2. 修改 package.json 配置 3. 添加 npm 脚本: 4.使用 Husky…

ETL数据集成丨主流ETL工具(ETLCloud、DataX、Kettle)数据传输性能大PK

目前市面上的ETL工具众多&#xff0c;为了方便广大企业用户在选择ETL工具时有一个更直观性能方面的参考值&#xff0c;我们选取了目前市面上最流行的三款ETL工具&#xff08;ETLCloud、DataX、Kettle&#xff09;来作为本次性能传输的代表&#xff0c;虽然性能测试数据有很多相…

类和对象 中篇

类和对象 中篇 ​ 在上篇中&#xff0c;我们介绍了类的基础部分&#xff0c;本篇我们讲解C类的六大默认成员函数 ​ 所谓默认成员函数&#xff0c;就是我们不写编译器也会自动生成&#xff0c;自动调用的函数。而自动生成的函数对内置类型的成员不会处理(有些高版本编译器会…

FreeRTOS 入门 知识

什么是FreeRTOS FreeRTOS 是一个轻量级的实时操作系统&#xff08;RTOS&#xff09;&#xff0c;由 Richard Barry 在 2003 年开发&#xff0c;并且由亚马逊的 FreeRTOS 项目&#xff08;一个由 Amazon Web Services (AWS) 支持的开源项目&#xff09;进一步推动和发展。FreeR…

python开发遇到的坑汇总

文章目录 1.点击导入操作&#xff0c;所有配置全没了 1.点击导入操作&#xff0c;所有配置全没了 在 PyCharm 中&#xff0c;如果你遇到了点击导入&#xff08;import&#xff09;操作后&#xff0c;项目似乎进行了重新安装或重新部署的情况&#xff0c;这通常不是由简单的导入…

在Linux上设置MySQL允许远程连接的完整指南

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

Vue3+Vite+TS+Axios整合详细教程

1. Vite 简介 Vite是新一代的前端构建工具&#xff0c;在尤雨溪开发Vue3.0的时候诞生。类似于Webpack Webpack-dev-server。其主要利用浏览器ESM特性导入组织代码&#xff0c;在服务器端按需编译返回&#xff0c;完全跳过了打包这个概念&#xff0c;服务器随起随用。生产中利用…

达梦数据库的系统视图v$sessions

达梦数据库的系统视图v$sessions 达梦数据库&#xff08;DM Database&#xff09;是中国的一款国产数据库管理系统&#xff0c;它提供了类似于Oracle的系统视图来监控和管理数据库。V$SESSIONS 是达梦数据库中的一个系统视图&#xff0c;用于显示当前数据库会话的信息。 以下…

数据结构(Java):LinkedList集合Stack集合

1、集合类LinkedList 1.1 什么是LinkedList LinkedList的底层是一个双向链表的结构&#xff08;故不支持随机访问&#xff09;&#xff1a; 在LinkedList中&#xff0c;定义了first和last&#xff0c;分别指向链表的首节点和尾结点。 每个节点中有一个成员用来存储数据&…

Oracle 物化视图详解与实践

一.视图是什么&#xff1f; 普通视图只是存放在数据字典当中的子查询&#xff0c;本身没有数据&#xff0c;数据来源于基表&#xff0c;可以把普通视图当作编译好的sql语句。 二.物化视图是什么&#xff1f; 把查询到的数据存放起来&#xff0c;为了以后再次查询使用&#xff0…

NiFi1.25版本HTTPS模式下RestAPI使用入门

Apache NiFi 是一个强大的数据流处理工具&#xff0c;通过其 REST API&#xff0c;用户可以远程管理和控制数据流处理器。本文将介绍如何使用 NiFi 1.25 版本HTTPS 模式下Rest API&#xff0c;包括获取 token、获取组件信息、启动和停止组件、以及更改组件的调度频率等操作。 …

2.2动态库

动态库的特性 位置无关代码(PIC - position independent code) 库中的符号&#xff0c;不需要提前存储在可执行文件中&#xff0c;只有当链接时&#xff0c;可执行文件才会将需要的代码函数链接到内存中。 制作动态库 编写主函数 test.c #include<stdio.h> int main…

Java 设计模式系列:解释器模式

简介 解释器模式是一种行为型设计模式&#xff0c;它提供了一种构建抽象语法树的机制&#xff0c;并定义了如何解释这棵树。解释器模式属于编译原理中的语法制导翻译的范畴。 如上图&#xff0c;设计一个软件用来进行加减计算。我们第一想法就是使用工具类&#xff0c;提供对应…