vue 等比例截图组件,支持缩放和旋转
<template>
<div class="crop-image" :style="wrapStyle">
<img :src="url" alt="" crossOrigin="anonymous" :style="imgStyle" v-if="url">
<canvas ref="canvas" @mousedown="onmousedown" @mousemove="onmousemove" @mousewheel="onmousewheel" ></canvas>
</div>
</template>
<script>
export default {
name:"crop-image",
props:{
opt:{
type:Object,
default(){
return {
width:400,
height:400,
addScale:0.06 // 缩放的速度
};
}
}
},
data(){
return {
img:null,
url:null,
imgInfo: null, // 压缩前的信息
imgCInfo:null, // 压缩后的信息
clipInfo:null, // 压缩前的信息
clipCinfo:null, // 压缩后的信息
ctx:null, // 画板
pos:{x:0,y:0},
deg:0
}
},
computed:{
wrapStyle(){
return {
width:`${this.opt.width}px`,
height:`${this.opt.height}px`
}
},
imgStyle(){
let imgCInfo = this.imgCInfo;
if(imgCInfo){
return {
width: `${imgCInfo.w}px`,
height: `${imgCInfo.h}px`,
left:`${this.pos.x}px`,
top:`${this.pos.y}px`,
transform: `rotateZ(${this.deg}deg)`,
transformOrigin: "center"
}
}
return {height:"0px",height:"0px",display:"none"};
}
},
mounted(){
let canvas = this.$refs.canvas;
canvas.width = this.opt.width;
canvas.height = this.opt.height;
this.ctx = canvas.getContext("2d");
window.addEventListener("mouseup",this.onmouseup);
},
methods:{
init(src,bound){
this.setSrc(src)
this.setClip(bound);
}, loadImage(url,attrs){ let img = new Image();
img.src = url;
attrs = attrs || {};
for(let i in attrs){
img[i] = attrs[i];
}
return new Promise((resolve,reject)=>{
img.onload = function(){
resolve(img);
};
img.onerror = reject;
});
},
compress(target){
// 压缩比例
let opt = this.opt;
let wRate = opt.width/opt.height;
let tRate = target.width/target.height;
let w,h,scale;
if(wRate > tRate){
h = opt.height;
scale = h/target.height;
w = scale * target.width;
}else{
w = opt.width;
scale = w / target.width;
h = scale * target.height;
}
return {
w,h,scale
}
},
// 外部调用
async setSrc(src){
let img = await this.loadImage(src,{crossOrigin:"anonymous"});
this.url = src;
this.img = img;
this.setImg(img);
},
// 外部调用
setClip(clipInfo){
if(clipInfo){
this.clipInfo = {
w:clipInfo.width,
h:clipInfo.height
};
this.clipCinfo = this.compress(clipInfo);
this.clipCinfo.x = (this.opt.width - this.clipCinfo.w)/2;
this.clipCinfo.y = (this.opt.height - this.clipCinfo.h)/2;
this.fill();
}
},
async exportBase(){
// 导出图片 base64
let deg = this.deg;
let pos = this.pos;
let clipCinfo = this.clipCinfo; let imgCInfo = this.imgCInfo; // 压缩后的宽高
let imgInfo = this.imgInfo; // 原始的宽高 let scale = imgCInfo.scale;
let sx = clipCinfo.x - pos.x;
let sy = clipCinfo.y - pos.y;
let canvas = document.createElement("canvas");
canvas.width = parseInt(clipCinfo.w / scale);
canvas.height = parseInt(clipCinfo.h / scale);
let swidth = imgInfo.w;
let sheight = imgInfo.h;
if(swidth < canvas.width){
swidth = canvas.width
}
if(sheight < canvas.height){
sheight = canvas.height
}
let ctx = canvas.getContext("2d");
let img = this.img;
if(deg != 0){
img = await this.loadImage(this.getRotateImg());
if(deg == 90 || deg == 270){
// 竖着的,需要坐标转换
let x = pos.x + (imgCInfo.w -imgCInfo.h)/2;
let y = pos.y + (imgCInfo.h -imgCInfo.w)/2;
sx = clipCinfo.x - x;
sy = clipCinfo.y - y;
}
}
sx /= scale;
sy /= scale;
ctx.drawImage(img,sx,sy,swidth,sheight,0,0,swidth,sheight);
return canvas.toDataURL("image/png");
},
rotate(flag){
// 旋转
let deg = this.deg;
if(flag){
deg += 90;
}else{
deg -= 90;
}
if(deg >= 360){
deg -= 360;
}
if(deg <= 0){
deg += 360;
}
this.deg = deg;
},
getRotateImg(){
// 获取旋转后的图片
let deg = this.deg;
let imgInfo = this.imgInfo;
let canvas = document.createElement("canvas");
let w,h;
if(deg == 90 || deg == 270){
// 垂直
w = imgInfo.h;
h = imgInfo.w;
}else{
// 横着的
w = imgInfo.w;
h = imgInfo.h;
}
canvas.width = w;
canvas.height = h;
let ctx = canvas.getContext("2d");
ctx.translate(w/2,h/2);
ctx.rotate(deg * Math.PI/180 );
ctx.drawImage(this.img,0,0,imgInfo.w,imgInfo.h,-imgInfo.w/2,-imgInfo.h/2,imgInfo.w,imgInfo.h);
return canvas.toDataURL("image/png");
},
setImg(img){
this.imgInfo = {
w:img.width,
h:img.height
};
this.imgCInfo = this.compress(img);
let x = (this.opt.width - this.imgCInfo.w)/2;
let y = (this.opt.height - this.imgCInfo.h)/2;
this.pos = {
x,y
};
}, onmouseup(){
this.mouse = null;
},
onmousedown(e){
this.mouse = {
x:e.offsetX,
y:e.offsetY
}
},
onmousemove(e){
if(this.mouse){
this.pos.x += e.offsetX - this.mouse.x ;
this.pos.y += e.offsetY - this.mouse.y;
this.mouse = {
x:e.offsetX,
y:e.offsetY
};
}
},
onmousewheel(event){
event.preventDefault();
const direction = (event.wheelDelta || -event.detail) > 0 ? 1 : 0;
let addScale = this.opt.addScale || 0.06;
let w,h,scale;
if(direction){
// 放大
w = this.imgCInfo.w * (1 + addScale);
}else{
// 缩小
w = this.imgCInfo.w * (1 - addScale);
}
if(w < 10){
w = 10;
}
scale = w/this.imgInfo.w;
h = this.imgInfo.h * scale;
let x = this.pos.x - (w - this.imgCInfo.w)/2;
let y = this.pos.y - (h - this.imgCInfo.h)/2;
this.pos = {
x,y
};
this.imgCInfo = {
scale,
w,
h
}
},
fill(){
let {w,h,x,y} = this.clipCinfo;
let clipctx = this.ctx;
let opt = this.opt;
clipctx.clearRect(0, 0, opt.width, opt.height);
clipctx.beginPath();
clipctx.fillStyle = 'rgba(0,0,0,0.6)';
clipctx.strokeStyle = "green";
//遮罩层
clipctx.globalCompositeOperation = "source-over";
clipctx.fillRect(0, 0, opt.width, opt.height);
//画框
clipctx.globalCompositeOperation = 'destination-out';
clipctx.fillStyle = 'rgba(0,0,0,1)';
clipctx.fillRect(x, y, w, h);
//描边
clipctx.globalCompositeOperation = "source-over";
clipctx.moveTo(x, y);
clipctx.lineTo(x + w, y);
clipctx.lineTo(x + w, y + h);
clipctx.lineTo(x, y + h);
clipctx.lineTo(x, y);
clipctx.stroke();
clipctx.closePath();
}
}
}
</script>
<style lang="scss" scoped>
.crop-image{
border: 1px solid rgb(35, 184, 255);
margin: 20px auto;
position: relative;
overflow: hidden;
background: url(data:image/png;base64,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) center repeat;
canvas{
width: 100%;
height: 100%;
position: relative;
z-index: 2;
cursor: move;
}
img{
position: absolute;
z-index: 1;
}
}
</style>
初始化:
this.$refs.cropImage.init("图片路径",{width:80,height:120});
旋转:
this.$refs.cropImage.rotate()
导出等比例的图片:
this.src = await this.$refs.cropImage.exportBase();
vue 等比例截图组件,支持缩放和旋转的更多相关文章
- 小程序canvas截图组件
最近做一个小程序的过程中,需要用到截图功能,网上搜了一下,发现没有符合要求的,就自己搞了个组件,方便复用. 目前功能很简单,传入宽高和图片路径即可,宽高是为了计算截图的比例,只支持缩放和移动. 实现思 ...
- 【Java实例】使用Thumbnailator生成缩略图(缩放、旋转、裁剪、水印)
1 需求 表哥需要给儿子报名考试,系统要求上传不超过30KB的图片,而现在的手机随手一拍就是几MB的,怎么弄一个才30KB的图片呢? 一个简单的办法是在电脑上把图片缩小,然后截屏小图片,但现在的电脑屏 ...
- 整理目前支持 Vue 3 的 UI 组件库 (2021 年)
最近,让前端圈子振奋的消息莫过于 Vue 3.0 的发布,一个无论是性能还是 API 设计都有了重大升级的新版本.距离 Vue 3.0 正式版发布已经有一段时间了,相信相关生态周边库也正在适配新版本中 ...
- 基于Vue.js PC桌面端弹出框组件|vue自定义弹层组件|vue模态框
vue.js构建的轻量级PC网页端交互式弹层组件VLayer. 前段时间有分享过一个vue移动端弹窗组件,今天给大家分享一个最近开发的vue pc端弹出层组件. VLayer 一款集Alert.Dia ...
- Vue.js——60分钟组件快速入门(下篇)
概述 上一篇我们重点介绍了组件的创建.注册和使用,熟练这几个步骤将有助于深入组件的开发.另外,在子组件中定义props,可以让父组件的数据传递下来,这就好比子组件告诉父组件:"嘿,老哥,我开 ...
- Android实现支持缩放平移图片
本文主要用到了以下知识点 Matrix GestureDetector 能够捕捉到长按.双击 ScaleGestureDetector 用于检测缩放的手势 自由的缩放 需求:当图片加载时,将图片在屏幕 ...
- Vue.js学习 Item11 – 组件与组件间的通信
什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有 ...
- vue实现简单表格组件
本来想这一周做一个关于vuex的总结的,但是由于朋友反应说还不知道如何用vue去写一个组件,所以在此写写一篇文章来说明下如何去写vue页面或者组件.vue的核心思想就是组件,什么是组件呢?按照我的理解 ...
- 一个 Vue 的滑动按钮组件
git 地址:https://github.com/SyMind/vue-sliding-button vue-better-slider 一个 Vue 的滑动按钮组件,有关滑动方面的处理借鉴 bet ...
随机推荐
- Kettle建立数据库链接报错-'MS SQL Server' driver (jar file) is installed. kettle的bug,对于12.2而言
1.链接sql server数据库报错 错误连接数据库 [My_vm_win_sql] : org.pentaho.di.core.exception.KettleDatabaseException: ...
- OpenJudge计算概论-奇偶排序
/*==============================================总时间限制: 1000ms 内存限制: 65536kB描述 输入十个整数,将十个整数按升序排列输出,并且 ...
- PHP7 MongoDB 使用方法
原文链接: http://www.zhaokeli.com/article/8574.html MongoDb原生操作 Mongodb连接 PHP7 连接 MongoDB 语法如下: 复制代码 $ma ...
- PHP 类属性
属性 (Properties) 类的变量成员叫做“属性”,或者叫“字段”.“特征”,在本文档统一称为“属性”.属性声明是由关键字 public,protected或者 private 开头,然后跟一个 ...
- 004-行为型-07-备忘录模式(Memento)
一.概述 又叫做快照模式(Snapshot Pattern)或Token模式 保存对象的内部状态,并在需要的时候(undo/rollback)恢复对象以前的状态. 意图:在不破坏封装性的前提下,捕获一 ...
- realsense SDK编译 debug
1>------ 已启动全部重新生成: 项目: ZERO_CHECK, 配置: Debug x64 ------1> Checking Build System1> CMake do ...
- netty5心跳与阻塞性业务消息分发实例
继续之前的例子(netty5心跳与业务消息分发实例),我们在NettyClientHandler把业务消息改为阻塞性的: package com.wlf.netty.nettyclient.handl ...
- C++数据存储方式
1.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区,里面的变量通常是局部变量.函数参数等. 2.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去 ...
- Spring Boot应用的打包和部署
传统的Web应用在发布之前通常会打成WAR包,然后将WAR包部署到Tomcat等容器中使用,而通过前面的学习我们已经知道,Spring Boot应用既能以JAR包的形式部署,又能以WAR包的形式部署. ...
- c# Invoke的新用法
在C# 3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法..NET Framework 3.5及以后版本更能用Action封装方法.例如以下写法可以看上去非常简洁: voi ...