头像剪切上传

news2024/9/23 3:26:31

头像剪切上传

    • 文章说明
    • 核心Api
    • 示例源码
    • 效果展示
    • 源码下载

文章说明

本文主要为了学习头像裁剪功能,以及熟悉canvas绘图和转文件的相关操作,参考教程(Web渡一前端–图片裁剪上传原理)

核心Api

主要就一个在canvas绘图的操作
context.drawImage(image, imgX, imgY, rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height);

以及canvas转为file对象的操作
let formData = new FormData();
const file = new File([blob], data.selectFileName, {type: data.selectFileType})
formData.append(“file”, file);

关于其中的绘制区域的大小缩放以及移动,也算是一个小难点;一般也有另一种裁剪区域风格,即四条线风格,可通过代码进行理解

示例源码

AvatarUpload.vue

<template>
  <div class="container">
    <div class="img-container">
      <div class="select-file" @click="selectFile" v-if="!data.selectFile">
        <p>jpg/png file with a size less than 5MB<em>click to upload</em></p>
      </div>
      <img alt="" :src="data.src" class="img" v-if="data.selectFile" draggable="false"
           :style="{'height' : data.imgHeight }"/>
      <div class="rectangle" v-if="data.selectFile" @mousedown="dragStart($event)" @mousemove="changePos($event)"
           @mousewheel="wheel($event)"></div>
    </div>

    <canvas width="100" height="100" class="canvas"></canvas>

    <el-button type="primary" class="upload-button" @click="uploadAvatar">上传</el-button>
  </div>
</template>

<script>
import {onBeforeUnmount, onMounted, reactive} from "vue";
import {axiosRequest, message} from "@/util/api";
import {MethodType} from "@/util/constant";

export default {
  setup: function () {
    const data = reactive({
      src: null,
      imgHeight: "300px",
      selectFile: false,
      selectFileName: "",
      selectFileType: "",
    });

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

        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;
          setTimeout(() => {
            data.imgHeight = image.height + "px";

            rectangle = imgContainer.getElementsByClassName("rectangle")[0];
            rectangleWidth = rectangle.clientWidth;
            rectangleHeight = rectangle.clientHeight;

            context.drawImage(image, (image.width / 2 - rectangleWidth / 2), (image.height / 2 - rectangleHeight / 2), rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height);
          }, 0);
        };
      } catch (e) {
        if (!(e.name === 'AbortError' && e.message === 'The user aborted a request.')) {
          throw e;
        }
      }
    }

    let isDragging = false;
    let mouseUpListener;
    let containerX;
    let containerY;
    let imgContainer;
    let imgWidth;
    let imgHeight;

    let rectangle;
    let initialX;
    let initialY;
    let rectangleWidth;
    let rectangleHeight;
    let imgX;
    let imgY;

    let headerHeight;

    let canvas;
    let context;
    let image;

    function dragStart(e) {
      isDragging = true;

      containerX = imgContainer.offsetLeft;
      containerY = imgContainer.offsetTop;
      imgWidth = imgContainer.clientWidth;
      imgHeight = imgContainer.clientHeight;

      initialX = e.offsetX;
      initialY = e.offsetY;

      rectangle = imgContainer.getElementsByClassName("rectangle")[0];
      rectangleWidth = rectangle.clientWidth;
      rectangleHeight = rectangle.clientHeight;
    }

    function changePos(e) {
      if (!isDragging) {
        return;
      }

      const x = e.clientX - containerX - initialX + rectangleWidth / 2;
      const y = e.clientY - containerY - initialY - headerHeight + rectangleHeight / 2;

      if (x >= rectangleWidth / 2 + 3 && x < imgWidth - rectangleWidth / 2 - 2) {
        imgX = x - rectangleWidth / 2;
        rectangle.style.left = x + "px";
        centerX = imgX + rectangleWidth / 2;
        context.drawImage(image, imgX, imgY, rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height);
      }
      if (y >= rectangleHeight / 2 + 3 && y < imgHeight - rectangleHeight / 2 - 4) {
        imgY = y - rectangleHeight / 2;
        rectangle.style.top = y + "px";
        centerY = imgY + rectangleHeight / 2;
        context.drawImage(image, imgX, imgY, rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height);
      }
    }

    onMounted(() => {
      mouseUpListener = () => {
        isDragging = false;
      }
      document.addEventListener("mouseup", mouseUpListener);

      const containerList = document.getElementsByClassName("container");
      const container = containerList[containerList.length - 1];
      headerHeight = container.parentNode["getBoundingClientRect"]().y;

      imgContainer = container.getElementsByClassName("img-container")[0];

      canvas = container.getElementsByClassName("canvas")[0];
      context = canvas.getContext("2d");
    });

    onBeforeUnmount(() => {
      document.removeEventListener("mouseup", mouseUpListener);
    });

    const gap = 2;
    const minRange = 20;
    let centerX;
    let centerY;

    function wheel(e) {
      if (!centerX) {
        centerX = image.width / 2;
      }
      if (!centerY) {
        centerY = image.height / 2;
      }

      if (e.deltaY > 0) {
        if (rectangleWidth + gap >= image.width || rectangleHeight + gap >= image.height) {
          return;
        }
        if ((centerX - rectangleWidth / 2 - gap < 0) || (centerY - rectangleHeight / 2 - gap < 0)) {
          return;
        }

        rectangleWidth += gap;
        rectangleHeight += gap;
        rectangle.style.width = rectangleWidth + "px";
        rectangle.style.height = rectangleHeight + "px";
      } else {
        if (rectangleWidth - gap < minRange || rectangleHeight - gap < minRange) {
          return;
        }
        rectangleWidth -= gap;
        rectangleHeight -= gap;
        rectangle.style.width = rectangleWidth + "px";
        rectangle.style.height = rectangleHeight + "px";
      }

      context.drawImage(image, (centerX - rectangleWidth / 2), (centerY - rectangleHeight / 2), rectangleWidth, rectangleHeight, 0, 0, canvas.width, canvas.height);
    }

    function uploadAvatar() {
      if (!data.selectFile) {
        message("请先选择图片", "info");
        return;
      }
      canvas.toBlob((blob) => {
        let formData = new FormData();
        const file = new File([blob], data.selectFileName, {type: data.selectFileType})
        formData.append("file", file);

        axiosRequest(MethodType.post, "/user/uploadAvatar", formData, (res) => {
          message(res.data.msg, "info");
        });
      }, data.selectFileType);
    }

    return {
      data,
      selectFile,
      dragStart,
      changePos,
      wheel,
      uploadAvatar,
    }
  }
}
</script>

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

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

.rectangle {
  width: 100px;
  height: 100px;
  border: 1px dashed #409eff;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
  z-index: 999;
  box-shadow: #888888 0 0 1px 1px;
  cursor: pointer;
}

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

.select-file:hover {
  border: 1px dashed #409eff;
  cursor: pointer;
}

.select-file p {
  font-size: 14px;
  color: #606266;
}

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

.img {
  border-radius: 20px;
  border: 1px dashed #409eff;
}

.canvas {
  margin-left: 100px;
  border: 1px dashed #409eff;
  float: left;
  border-radius: 50%;
}

.upload-button {
  position: absolute;
  width: 180px;
  height: 50px;
  top: 460px;
  font-size: 20px;
}
</style>

效果展示

在这里插入图片描述

关于裁剪区域的风格,设置为四条线可移动那种,需要改动一些代码,考虑后续补充

源码下载

参见Gitee链接(WEB-OS-SYSTEM)

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

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

相关文章

【触想智能】工业一体机刷卡应用知识分享

工业一体机刷卡技术是一种高效、稳定、安全的身份认证方式&#xff0c;具有广泛的应用场景和优势。在工业自动化控制、生产过程监测等领域&#xff0c;它已成为必不可少的设备之一。 一、工业一体机刷卡的原理:工业一体机刷卡的原理和普通的刷卡设备类似&#xff0c;都是通过读…

VS2022如何添加行号?(VS2022不显示行号解决方法)

VS2022不显示行号解决方法 VS2022是非常好用的工具&#xff0c;很多同学在初学C/C的时候&#xff0c;都会安装&#xff0c;默认安装好VS2022后&#xff0c;写代码时&#xff0c;在编辑框的窗口左边就有显示行号&#xff0c;如下图所示&#xff1a; 但是有些同学安装好后&#…

阿里云中小企业扶持权益

为企业提供云资源和技术服务&#xff0c;助力企业开启智能时代创业新范式。阿里云推出中小企业扶持权益 上云必备&#xff0c;助力企业长期低成本用云 一、ECS-经济型e实例、ECS u1实例活动规则 活动时间 2023年10月31日0点0分0秒至2026年3月31日23点59分59秒 活动对象 同时满…

GEE:使用Sigmoid激活函数对单波段图像进行变换(以NDVI为例)

作者:CSDN @ _养乐多_ 本文将介绍在 Google Earth Engine (GEE)平台上,对任意单波段影像进行 Sigmoid 变换的代码。并以对 NDVI 影像像素值的变换为例。 文章目录 一、Sigmoid激活函数1.1 什么是 Sigmoid 激活函数1.2 用到遥感图像上有什么用?二、代码链接三、完整代码一…

Tomcat概念、安装及相关文件介绍

目录 一、web技术 1、C/S架构与B/S架构 1.1 http协议与C/S架构 1.2 http协议与B/S架构 2、前端三大核心技术 2.1 HTML&#xff08;Hypertext Markup Language&#xff09; 2.2 css&#xff08;Cascading Style Sheets&#xff09; 2.3 JavaScript 3、同步和异步 4、…

吴恩达机器学习全课程笔记第七篇

目录 前言 P114-P120 推荐系统 协同过滤 均值归一化 协同过滤的tensorflow实现 查找相关项目 P121-P130 基于内容的过滤 强化学习 P131-P142 状态动作值函数定义 Bellman方程 随机环境 连续状态空间应用实例 前言 这是吴恩达机器学习笔记的第七篇&#xff0c;…

linux kernel物理内存概述(二)

目录 物理内存数据结构 设备数物理内存描述 物理内存映射 map_kernel map_mem zone数据结构 zone类型 物理内存数据结构 站在处理器角度&#xff0c;管理物理内存的最小单位是页面。使用page数据结构描述&#xff0c;通常默认大小4kB&#xff0c;采用mem_map[]数组来存…

(每日持续更新)jdk api之PipedWriter基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

合作的终极策略,竟如此有数学规律?《多Agent系统引论》第6章 多Agent交互 原文注释

6.0 前言 本文介绍一下多Agent交互过程中的一些概念&#xff0c;并且我保证能给你在人类社会中的工作生活学习带来启发。 6.1 效用和偏好 6.1.1 不知道什么是效用&#xff1f;那我告诉你什么是边际效应递减&#xff01; 想象一个人&#xff0c;他总资产只有1块钱&#xff0c;…

Vue前端+快速入门【详解】

目录 1.Vue概述 2. 快速入门 3. Vue指令 4.表格信息案例 5. 生命周期 1.Vue概述 1.MVVM思想 原始HTMLCSSJavaScript开发存在的问题&#xff1a;操作麻烦&#xff0c;耦合性强 为了实现html标签与数据的解耦&#xff0c;前端开发中提供了MVVM思想&#xff1a;即Model-Vi…

什么是 End-to-End 测试?

在使用 vue 的模板创建新项目的时候&#xff0c;有一个选项是问&#xff0c;是否添加“端到端”测试&#xff1f;说实在我不知道&#xff0c;而且三个选项一个都不认识。 ? Add an End-to-End Testing Solution? › - Use arrow-keys. Return to submit. ❯ NoCypressNigh…

QChart柱状图

//柱状图// 创建柱状图数据QBarSet *set0 new QBarSet("");*set0 << 1601 << 974 << 655 << 362;QBarSeries *series new QBarSeries();series->append(set0);set0->setColor(QColor("#F5834B"));// 创建柱状图QChart *ch…

基于springboot+vue的美食烹饪互动平台

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Anthropic

文章目录 关于 Anthropic公司产品anthropic-sdk-python 基本使用 关于 Anthropic 官网&#xff1a;https://www.anthropic.comhuggingface : http://huggingface.co/Anthropicgithub : https://github.com/anthropics https://github.com/anthropics/anthropic-sdk-python官方…

【MySQL】深入解析日志系统:undo log、redo log、bin log

文章目录 前言1、undo log1.1、undo log 是什么1.2、事务回滚 2、redo log2.1、redo log 是什么2.2、redo log 刷盘2.3、redo log 硬盘文件 3、bin log3.1、bin log 是什么3.2、bin log 和 redo log 区别3.3、bin log 刷盘3.4、两阶段提交 前言 MySQL数据库提供了功能强大的日…

6.3 业务分析方法 (15%)

一、业务分析方法 1、客户分析 用户从哪里来到哪里去&#xff1b;来源于各个渠道&#xff1b; 分析: 投入产出比&#xff1a;微博&#xff1e;公众号 展示量&#xff1a;微博低于公众号&#xff1b;若增加品牌曝光率&#xff0c;可用公众号渠道 跳失率&#xff1a;微博低于公…

C# Winform画图绘制圆形

一、因为绘制的圆形灯需要根据不同的状态切换颜色,所以就将圆形灯创建为用户控件 二、圆形灯用户控件 1、创建用户控件UCLight 2、设值用户控件大小(30,30)。放一个label标签,AutoSize为false(不自动调整大小),Dock为Fill(填充),textaglign为居中显示。 private Color R…

【leetcode】删除链接的倒数第N个节点

/*** Definition for singly-linked list.* function ListNode(val, next) {* this.val (valundefined ? 0 : val)* this.next (nextundefined ? null : next)* }*/ /*** param {ListNode} head* param {number} n* return {ListNode}*/ var removeNthFromEnd fun…

赋能中国制造,大道云行发布智能制造分布式存储解决方案

《中国制造2025》指出&#xff0c;“制造业是国民经济的主体&#xff0c;是立国之本、兴国之器、强国之基。” 智能制造引领产业提质增效 智能制造是一种利用先进的信息技术、自动化技术和智能技术来优化和升级制造业生产过程的方法。它将人工智能、大数据、物联网、机器学习等…

3DEXPERIENCE Works八大核心优势分析

云技术正在加速普及&#xff0c;助力各行各业数字化转型。根据IDC 2023年12月发布的报告&#xff0c;2023年全球云计算市场规模达到3329亿美元&#xff0c;同比增长19.4%。其中&#xff0c;公有云市场规模达到2587亿美元&#xff0c;同比增长21.5%;私有云市场规模达到742亿美元…