修正版头像上传组件

修正版头像上传组件

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

文章说明

在头像剪切上传一文中,我采用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.mfbz.cn/a/796769.html

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

相关文章

ChatGPT使用姿势

使用上的痛点 用的不好&#xff1a;你经常会感觉到 ChatGPT 回答的好空&#xff0c;没有太多参考价值无处去用&#xff1a;有了 GPT 之后&#xff0c;发现自己好像并没有什么好问的&#xff0c;不知道可以用 GPT 来干嘛。 如何使用AI 核心心法&#xff1a;GPT 生成的答案质量…

纯技术分享:淘宝商品详情原数据接口参数解析

item_get_app-获得淘宝app商品详情原数据 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item_search_s…

【机器学习】使用决策树分类器预测汽车安全性的研究与分析

文章目录 一、决策树算法简介决策树的结构分类和回归树 (CART)决策树算法术语决策树算法直觉 二、属性选择度量信息增益熵 基尼指数计算分割基尼指数的步骤 三、决策树算法中的过度拟合避免过度拟合的方法 四、导入库和数据可视化探索性数据分析重命名列名查看数据集的总结信息…

WAF基础介绍

WAF 一、WAF是什么&#xff1f;WAF能够做什么 二 waf的部署三、WAF的工作原理 一、WAF是什么&#xff1f; WAF的全称是&#xff08;Web Application Firewall&#xff09;即Web应用防火墙&#xff0c;简称WAF。 国际上公认的一种说法是&#xff1a;Web应用防火墙是通过执行一…

小零食,大智慧!连锁零食店如何选择收银?收银系统源码

近几年专业的散装零食店非常的火热&#xff0c;像百草味、良品铺子、大嘴零食、来伊份等都大受欢迎。而传统超市的散装零食区则是日益冷落&#xff0c;小超市多数干脆放弃了散装。 休闲零食作为快消品的一类&#xff0c;是大家工作闲暇、生活休闲的必备食品。随着人们生活质量…

前端状态管理工具pinia:pinia是什么?相较于Vuex,pinia有什么优势,如何手动添加pinia到Vue3项目中

1.什么是pinia? Pinia是Vue的最新状态管理工具&#xff0c;是Vuex的替代品。 2.相较于Vuex,pinia有什么优势? 1.提供更加简单的API(去掉了mutation) 倘若你学习过vuex&#xff0c;你一定会发现很多很多不合理的地方,实现一个功能可能要在state定义数据&#xff0c;在muta…

【前端】零基础学会编写CSS

一、什么是CSS CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;是一种是一种用来为结构化文档&#xff08;如 HTML 文档&#xff09;添加样式&#xff08;字体、间距和颜色等&#xff09;的计算机语言&#xff0c;能够对网页中元素位置的排版进行像素级别的精…

前端练习小项目——方向感应名片

前言&#xff1a;在学习完HTML和CSS之后&#xff0c;我们就可以开始做一些小项目了&#xff0c;本篇文章所讲的小项目为——方向感应名片 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 在开始学习之前&#xff0c;先让我们看一…

卷积神经网络——LeNet——FashionMNIST

目录 一、文件结构二、model.py三、model_train.py四、model_test.py 一、文件结构 二、model.py import torch from torch import nn from torchsummary import summaryclass LeNet(nn.Module):def __init__(self):super(LeNet,self).__init__()self.c1 nn.Conv2d(in_channe…

基于SSM的校园一卡通管理系统的设计与实现

摘 要 本报告全方位、深层次地阐述了校园一卡通管理系统从构思到落地的整个设计与实现历程。此系统凭借前沿的 SSM&#xff08;Spring、Spring MVC、MyBatis&#xff09;框架精心打造而成&#xff0c;旨在为学校构建一个兼具高效性、便利性与智能化的一卡通管理服务平台。 该系…

liunx硬盘分区挂载笔记

NAME: 设备名称。 MAJ : 主设备号和次设备号。 RM: 只读标志&#xff08;0 表示可读写&#xff0c;1 表示只读&#xff09;。 SIZE: 设备的总大小。 RO: 只读状态&#xff08;0 表示可读写&#xff0c;1 表示只读&#xff09;。 TYPE: 设备类型&#xff08;disk 表示物理磁盘设…

C 语言结构体

由于近期项目需求,需使用到大量的指针与结构体&#xff0c;为更好的完成项目&#xff0c;故对结构体与指针的内容进行回顾&#xff0c;同时撰写本博客&#xff0c;方便后续查阅。 本博客涉及的结构体知识有&#xff1a; 1.0&#xff1a;结构体的创建和使用 2.0: typedef 关…

怎样在 C 语言中进行类型转换?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; &#x1f4d9;C 语言百万年薪修炼课程 通俗易懂&#xff0c;深入浅出&#xff0c;匠心打磨&#xff0c;死磕细节&#xff0c;6年迭代&#xff0c;看过的人都说好。 文章目…

记一次 .NET某上位视觉程序 离奇崩溃分析

一&#xff1a;背景 1. 讲故事 前段时间有位朋友找到我&#xff0c;说他们有一个崩溃的dump让我帮忙看下怎么回事&#xff0c;确实有太多的人在网上找各种故障分析最后联系到了我&#xff0c;还好我一直都是免费分析&#xff0c;不收取任何费用&#xff0c;造福社区。 话不多…

快速读出linux 内核中全局变量

查问题时发现全局变量能读出来会提高效率&#xff0c;于是考虑从怎么读出内核态的全局变量&#xff0c;脚本如下 f open("/proc/kcore", rb) f.seek(4) # skip magic assert f.read(1) b\x02 # 64 位def read_number(bytes):return int.from_bytes(bytes, little,…

每日一练:奇怪的TTL字段(python实现图片操作实战)

打开图片&#xff0c;只有四种数字&#xff1a;127&#xff0c;191&#xff0c;63&#xff0c;255 最大数字为255&#xff0c;想到进制转换 将其均转换为二进制&#xff1a; 发现只有前2位不一样 想着把每个数的前俩位提取出来&#xff0c;组成新的二进制&#xff0c;然后每…

c++ 多边形 xyz 数据 获取 中心点方法,线的中心点取中心值搞定 已解决

有需求需要对。多边形 获取中心点方法&#xff0c;绝大多数都是 puthon和java版本。立体几何学中的知识。 封装函数 point ##########::getCenterOfGravity(std::vector<point> polygon) {if (polygon.size() < 2)return point();auto Area [](point p0, point p1, p…

AI绘画Midijourney操作技巧及变现渠道喂饭式教程!

前言 盘点Midijourney&#xff08;AIGF&#xff09;热门赚米方法&#xff0c;总有一种适合你之AI绘画操作技巧及变现渠道剖析 【表情包制作】 首先我们对表情包制作进行详细的讲解&#xff1a; 当使用 Midjourney&#xff08;AIGF&#xff09; 绘画来制作表情包时&#xff…

ensp防火墙综合实验作业+实验报告

实验目的要求及拓扑图&#xff1a; 我的拓扑&#xff1a; 更改防火墙和交换机&#xff1a; [USG6000V1-GigabitEthernet0/0/0]ip address 192.168.110.5 24 [USG6000V1-GigabitEthernet0/0/0]service-manage all permit [Huawei]vlan batch 10 20 [Huawei]int g0/0/2 [Huawei-…

218.贪心算法:分发糖果(力扣)

核心思想 初始化每个学生的糖果数为1&#xff1a; 确保每个学生至少有一颗糖果。从左到右遍历&#xff1a; 如果当前学生的评分高于前一个学生&#xff0c;则当前学生的糖果数应比前一个学生多一颗。从右到左遍历&#xff1a; 如果当前学生的评分高于后一个学生&#xff0c;则…