html2canvas截图比例放大电路,利用vue如何实现裁切图片同时实现放大、缩小、旋转功能(详细教程)...

本文详细介绍了如何使用Vue实现图片裁切、旋转和缩放功能,包括利用html2canvas获取截图并进行比例放大,以及通过canvas进行图像操作。通过示例代码展示了关键操作,如监听鼠标事件、canvas图像处理等。

这篇文章主要介绍了vue实现裁切图片同时实现放大、缩小、旋转功能,现在分享给大家,也给大家做个参考。

本篇文章主要介绍了vue实现裁切图片同时实现放大、缩小、旋转功能,分享给大家,具体如下:

实现效果:裁切指定区域内的图片

旋转图片

放大图片

输出bolb 格式数据 提供给 formData 对象

效果图

00d1136d5a8050df4a824fdd0bf3607c.png

a7ee54cf5fb3a4d9a54212e8b7960083.png

87b56a63009f0dc892953a12abdc84ec.png

b1ed55ede4bc59f607576e4a79f9f0b6.png

b2360022e2c5f02f068e4a1e086c86aa.png

08ab7c2a035d8398dff167da36b13820.png

7b55bf22deac100d3e64a7cc7595e862.png

大概原理:

利用h5 FileReader 对象, 获取 “上传到浏览器的文件” ,文件形式 为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()

})

}

}

组件模板

:width="clip.width"

:height="clip.height"

@mousedown="handleClip($event)"

>

+

-

{{scale}}

upload

cancel

生成文件

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)})

}

}

}

@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;

}

}

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值