本文主要和大家介绍了vue实现裁切图片同时实现放大、缩小、旋转功能,小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助到大家。
实现效果:
裁切指定区域内的图片
旋转图片
放大图片
输出bolb 格式数据 提供给 formdata 对象
效果图
大概原理:
利用h5 filereader 对象, 获取 d9fae9a589f7d7312c0f67fffbcf927a “上传到浏览器的文件” ,文件形式 为base64形式, 把 base64 赋给canvas的上下文。
然后给canvas 元素上加入对(mousedown)监听事件。 当用户鼠标左键在canvas按下时:
挂载对 window 对象mousemove事件 ---> 获取 鼠标移动x,y距离.从而操作 canvas里的图像的位置移动。
挂载对 window 对象mouseup 事件, 清除 mousemove事件的绑定。(同时该事件触发后会被删除)
剩下的 放大、缩小 、 旋转 是对 canvas 对象的操作/坐标体系的操作。具体api详见mdn canvas 文档
代码
dom.js
export const on = ({el, type, fn}) => {
if (typeof window) {
if (window.addeventlistener) {
el.addeventlistener(type, fn, false)
} else {
el.attachevent(`on${type}`, fn)
}
}
}
export const off = ({el, type, fn}) => {
if (typeof window) {
if (window.addeventlistener) {
el.removeeventlistener(type, fn)
} else {
el.detachevent(`on${type}`, fn)
}
}
}
export const once = ({el, type, fn}) => {
const hyfn = (event) => {
try {
fn(event)
}
finally {
off({el, type, fn: hyfn})
}
}
on({el, type, fn: hyfn})
}
// 最后一个
export const fbtwice = ({fn, time = 300}) => {
let [ctime, k] = [null, null]
// 获取当前时间
const gettime = () => new date().gettime()
// 混合函数
const hyfn = () => {
const ags = argments
return () => {
cleartimeout(k)
k = ctime = null
fn(...ags)
}
}
return () => {
if (ctime == null) {
k = settimeout(hyfn(...arguments), time)
ctime = gettime()
} else {
if ( gettime() - ctime < 0) {
// 清除之前的函数堆 ---- 重新记录
cleartimeout(k)
k = null
ctime = gettime()
k = settimeout(hyfn(...arguments), time)
}
}}
}
export const contains = function(parentnode, childnode) {
if (parentnode.contains) {
return parentnode != childnode && parentnode.contains(childnode)
} else {
return !!(parentnode.comparedocumentposition(childnode) & 16)
}
}
export const addclass = function (el, classname) {
if (typeof el !== "object") {
console.log('el is not elem')
return null
}
let classlist = el['classname']
classlist = classlist === '' ? [] : classlist.split(/\s+/)
if (classlist.indexof(classname) === -1) {
classlist.push(classname)
el.classname = classlist.join(' ')
} else {
console.warn('warn classname current')
}
}
export const removeclass = function (el, classname) {
let classlist = el['classname']
classlist = classlist === '' ? [] : classlist.split(/\s+/)
classlist = classlist.filter(item => {
return item !== classname
})
el.classname = classlist.join(' ')
}
export const delay = ({fn, time}) => {
let ot = null
let k = null
return () => {
// 当前时间
let ct = new date().gettime()
const fixfn = () => {
k = ot = null
fn()
}
if (k === null) {
ot = ct
k = settimeout(fixfn, time)
return
}
if (ct - ot < time) {
ot = ct
cleartimeout(k)
k = settimeout(fixfn, time)
}
}
}
export const event = function () {
// 类型
this.typelist = {}
}
event.prototype.on = function ({type, fn}){
if (this.typelist.hasownproperty(type)) {
this.typelist[type].push(fn)
} else {
this.typelist[type] = []
this.typelist[type].push(fn)
}
}
event.prototype.off = function({type, fn}) {
if (this.typelist.hasownproperty(type)) {
let list = this.typelist[type]
let index = list.indexof(fn)
if (index !== -1 ) {
list.splice(index, 1)
}
} else {
console.warn('not has this type')
}
}
event.prototype.once = function ({type, fn}) {
const fixfn = () => {
fn()
this.off({type, fn: fixfn})
}
this.on({type, fn: fixfn})
}
event.prototype.trigger = function (type){
if (this.typelist.hasownproperty(type)) {
this.typelist[type].foreach(fn => {
fn()
})
}
}
组件模板
<template>
<p class="jc-clip-image" :style="{width: `${clip.width}`}">
<canvas ref="ctx"
:width="clip.width"
:height="clip.height"
@mousedown="handleclip($event)"
>
</canvas>
<input type="file" ref="file" @change="readfilemsg($event)">
<p class="clip-scale-btn">
<a class="add" @click="handlescale(false)">+</a>
<a @click="rotate" class="right-rotate">转</a>
<a class="poor" @click="handlescale(true)">-</a>
<span>{{scale}}</span>
</p>
<p class="upload-warp">
<a class="upload-btn" @click="dispatchupload($event)">upload</a>
<a class="upload-cancel">cancel</a>
</p>
<p class="create-canvas">
<a class="to-send-file" @click="outfile" title="请打开控制台">生成文件</a>
</p>
</p>
</template>
<script>
import {on, off, once} from '../../utils/dom'
export default {
ctx: null,
file: null,
x: 0, // 点击canvas x 鼠标地址
y: 0,// 点击canvas y 鼠标地址
xv: 0, // 鼠标移动 x距离
yv: 0, // 鼠标移动 y距离
nx: 0, // 原始坐标点 图像 x
ny: 0,// 原始坐标点 图像 y
img: null,
props: {
src: {
type: string,
default: null
},
clip: {
type: object,
default () {
return {width: '200px', height: '200px'}
}
}
},
data () {
return {
isshow: false,
base64: null,
scale: 1.5, //放大比例
deg: 0 //旋转角度
}
},
computed: {
width () {
const {clip} = this
return parsefloat(clip.width.replace('px', ''))
},
height () {
const {clip} = this
return parsefloat(clip.height.replace('px', ''))
}
},
mounted () {
const {$options, $refs, width, height} = this
// 初始化 canvas file nx ny
object.assign($options, {
ctx: $refs.ctx.getcontext('2d'),
file: $refs.file,
nx: -width / 2,
ny: -height / 2
})
},
methods: {
// 旋转操作
rotate () {
const {$options, draw} = this
this.deg = (this.deg + math.pi /2)% (math.pi * 2)
draw($options.img, $options.nx + $options.xv, $options.ny + $options.yv, this.scale, this.deg)
},
// 处理放大
handlescale (flag) {
const {$options, draw, deg} = this
flag && this.scale > 0.1 && (this.scale = this.scale - 0.1)
!flag && this.scale < 1.9 && (this.scale = this.scale + 0.1)
$options.img && draw($options.img, $options.nx + $options.xv, $options.ny + $options.yv, this.scale, deg)
},
// 模拟file 点击事件
dispatchupload (e) {
this.clearstate()
const {file} = this.$options
e.preventdefault()
file.click()
},
// 读取 input file 信息
readfilemsg () {
const {file} = this.$options
const {draw, createimage, $options: {nx, ny}, scale, deg} = this
const wfile = file.files[0]
const reader = new filereader()
reader.onload = (e) => {
const img = createimage(e.target.result, (img) => {
draw(img, nx, ny, scale, deg)
})
file.value = null
}
reader.readasdataurl(wfile)
},
// 生成 图像
createimage (src, cb) {
const img = new image()
this.$el.append(img)
img.classname = 'base64-hidden'
img.onload = () => {
cb(img)
}
img.src = src
this.$options.img = img
},
// 操作画布画图
draw (img, x = 0, y = 0, scale = 0.5,deg = math.pi ) {
const {ctx} = this.$options
let {width, height} = this
// 图片尺寸
let imgw = img.offsetwidth
let imgh = img.offsetheight
ctx.save()
ctx.clearrect( 0, 0, width, height)
ctx.translate( width / 2, height / 2, img)
ctx.rotate(deg)
ctx.drawimage(img, x, y, imgw * scale, imgh * scale)
ctx.restore()
},
// ... 事件绑定
handleclip (e) {
const {handlemove, $options, deg} = this
if (!$options.img) {
return
}
object.assign(this.$options, {
x: e.screenx,
y: e.screeny
})
on({
el: window,
type: 'mousemove',
fn: handlemove
})
once({
el: window,
type: 'mouseup',
fn: (e) =>{
console.log('down')
switch (deg) {
case 0: {
object.assign($options, {
nx: $options.nx + $options.xv,
ny: $options.ny + $options.yv,
xv: 0,
yv: 0
})
break;
}
case math.pi / 2: {
object.assign($options, {
nx: $options.ny + $options.yv,
ny: $options.nx - $options.xv,
xv: 0,
yv: 0
})
break;
}
case math.pi: {
object.assign($options, {
nx: $options.nx - $options.xv,
ny: $options.ny - $options.yv,
xv: 0,
yv: 0
})
break;
}
default: {
// $options.ny - $options.yv, $options.nx + $options.xv
object.assign($options, {
nx: $options.ny - $options.yv,
ny: $options.nx + $options.xv,
xv: 0,
yv: 0
})
}
}
off({
el: window,
type: 'mousemove',
fn: handlemove
})
}
})
},
// ... 处理鼠标移动
handlemove (e){
e.preventdefault()
e.stoppropagation()
const {$options, draw, scale, deg} = this
object.assign($options, {
xv: e.screenx - $options.x,
yv: e.screeny - $options.y
})
switch (deg) {
case 0: {
draw($options.img, $options.nx + $options.xv, $options.ny + $options.yv, scale, deg)
break;
}
case math.pi / 2: {
draw($options.img, $options.ny + $options.yv, $options.nx - $options.xv, scale, deg)
break;
}
case math.pi: {
draw($options.img, $options.nx - $options.xv, $options.ny - $options.yv, scale, deg)
break;
}
default: {
draw($options.img, $options.ny - $options.yv, $options.nx + $options.xv, scale, deg)
break;
}
}
},
// 清除状态
clearstate () {
const {$options, width, height} = this
if ($options.img) {
this.$el.removechild($options.img)
object.assign($options, {
x: 0,
y: 0,
xv: 0,
yv: 0,
nx: -width / 2,
ny: -height / 2,
img: null,
})
}
},
// 输出文件
outfile () {
const {$refs: {ctx}} = this
console.log(ctx.todataurl())
ctx.toblob((blob) => {console.log(blob)})
}
}
}
</script>
<style>
@component-namespace jc {
@component clip-image{
position: relative;
width: 100%;
canvas {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
box-shadow: 0 0 3px #333;
}
input {
display: none;
}
.base64-hidden {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: auto;
z-index: -999;
opacity: 0;
}
.clip-scale-btn {
position: relative;
@utils-clearfix;
margin-bottom: 5px;
text-align: center;
a {
float: left;
width: 20px;
height: 20px;
border-radius: 50%;
color: #fff;
background: #49a9ee;
text-align: center;
cursor: pointer;
}
&>.poor, &>.right-rotate {
float: right;
}
&>span{
position: absolute;
z-index: -9;
top: 0;
left: 0;
display: block;
position: relative;
width: 100%;
text-align: center;
height: 20px;
line-height: 20px;
}
}
.upload-warp {
@utils-clearfix;
.upload-btn,.upload-cancel {
float: left;
display:inline-block;
width: 60px;
height: 25px;
line-height: 25px;
color: #fff;
border-radius: 5px;
background: #49a9ee;
box-shadow: 0 0 0 #333;
text-align: center;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
cursor: pointer;
margin-top: 5px;
}
.upload-cancel{
background: gray;
float: right;
}
}
.to-send-file {
margin-top: 5px;
display: block;
width: 50px;
height: 25px;
line-height: 25px;
color: #fff;
border-radius: 5px;
background: #49a9ee;
cursor: pointer;
}
}
以上就是vue裁切图片实现放大、缩小、旋转功能代码的详细内容。