clipic
Version:
移动端图片裁剪工具
249 lines (235 loc) • 8.29 kB
JavaScript
import './style.css'
import dom from './dom'
class Clipic {
constructor() {
this.default = {
width: 500, // 裁剪宽度
height: 500, // 裁剪高度
src: '', // 需要裁剪的图片
encode: 'base64', // 导出格式,支持 base64|blob|file
type: 'jpeg', // 裁剪后图片的类型,仅支持jpeg/png两种
name: 'clipic', // 如果导出格式位file, 则可以填写图片名
quality: 0.9, // 压缩质量
buttonText: ['取消', '重置', '完成'] // 底部三个按钮文案
}
this.init() // 初始化,渲染dom跟css
this.clipic = this.getId('clipic')
this.img1 = this.getId('clipicImg1') // 背景图
this.img2 = this.getId('clipicImg2') // 前景图
this.frame1 = this.getId('clipicFrame1') // 背景操作框
this.frame2 = this.getId('clipicFrame2') // 前景操作框
this.cancelBtn = this.getId('clipicCancel') // 取消按钮
this.resetBtn = this.getId('clipicReset') // 重置按钮
this.confirmBtn = this.getId('clipicConfirm') // 完成按钮
this.reset = this.reset.bind(this)
this.done = this.done.bind(this)
this.cancel = this.cancel.bind(this)
}
init() {
if (!this.getId('clipic')) {
this.createHtml()
}
}
getId(id) {
return document.getElementById(id)
}
createHtml() {
const div = document.createElement('div')
div.className = 'clipic-body'
div.setAttribute('id', 'clipic')
div.innerHTML = dom
document.body.appendChild(div)
}
getImage(options = {}) {
// 初始化参数
this.scale = 1 // 缩放
this.rotate = 0 // 旋转
this.translateX = 0 // 水平偏移
this.translateY = 0 // 垂直偏移
const defaults = JSON.parse(JSON.stringify(this.default))
this.options = Object.assign(defaults, options)
this.cancelBtn.innerHTML = this.options.buttonText[0]
this.resetBtn.innerHTML = this.options.buttonText[1]
this.confirmBtn.innerHTML = this.options.buttonText[2]
this.img1.src = this.options.src
this.img2.src = this.options.src
let tempImage = new Image()
tempImage.onload = () => {
this.originW = this.img2.width
this.originH = this.img2.height
if (this.options.ratio) {
this.options.width = this.img2.width
this.options.height = this.img2.width / this.options.ratio
} else {
this.options.ratio = this.options.width / this.options.height
}
this.originRatio = this.originW / this.originH
this.initSize()
this.clipic.style.transform = 'translate(0, 0)'
setTimeout(() => {
if (this.options.ratio > this.originRatio) {
this.img1.style.width = this.frame2.clientWidth + 'px'
this.img2.style.width = this.frame2.clientWidth + 'px'
} else {
this.img1.style.height = this.frame2.clientHeight + 'px'
this.img2.style.height = this.frame2.clientHeight + 'px'
}
}, 300)
this.setTransform()
this.cancelBtn.addEventListener('click', this.cancel)
this.resetBtn.addEventListener('click', this.reset)
this.confirmBtn.addEventListener('click', this.done)
this.clipic.addEventListener('touchmove', e => {
e.preventDefault()
if (e.touches.length > 1) {
this.setScale(e.touches[0], e.touches[1])
this.setRotate(e.touches[0], e.touches[1])
return
}
this.setTranslate(e.touches[0])
})
this.clipic.addEventListener('touchend', e => {
this.distance = null
this.angle = null
this.moveX = null
this.moveY = null
})
}
tempImage.src = this.options.src
}
initSize() {
const body = document.documentElement || document.body
const cw = body.clientWidth - 60
const ch = body.clientHeight - 80
this.frame1.style.width = cw + 'px'
this.frame1.style.height = cw / this.options.ratio + 'px'
this.frame2.style.width = cw + 'px'
this.frame2.style.height = cw / this.options.ratio + 'px'
if (cw / this.options.ratio > ch) {
this.frame1.style.height = ch + 'px'
this.frame1.style.width = ch * this.options.ratio + 'px'
this.frame2.style.height = ch + 'px'
this.frame2.style.width = ch * this.options.ratio + 'px'
}
}
setScale(touches1, touches2) {
const x = Math.abs(touches1.clientX - touches2.clientX)
const y = Math.abs(touches1.clientY - touches2.clientY)
const s = Math.sqrt(x * x + y * y)
if (this.distance) {
this.scale += (s - this.distance) / this.img2.clientWidth
this.setTransform()
}
this.distance = s
}
setRotate(touches1, touches2) {
const x = touches1.clientX - touches2.clientX
const y = touches1.clientY - touches2.clientY
const s = Math.sqrt(x * x + y * y)
const angle = (Math.atan2(y, x) * 180) / Math.PI
if (this.angle) {
this.rotate += angle - this.angle
this.setTransform()
}
this.angle = angle
}
setTranslate(touches) {
const x = touches.clientX
const y = touches.clientY
if (this.moveX) {
this.translateX += x - this.moveX
}
if (this.moveY) {
this.translateY += y - this.moveY
}
this.moveX = x
this.moveY = y
this.setTransform()
}
setTransform() {
const transform = `translate(${this.translateX}px, ${this.translateY}px) scale(${this.scale}) rotate(${this.rotate}deg)`
this.img1.style.transform = transform
this.img2.style.transform = transform
}
cancel(eventType) {
this.clipic.style.transform = 'translate(0, 100%)'
setTimeout(() => {
this.img1.style = ''
this.img1.src = ''
this.img2.style = ''
this.img2.src = ''
}, 400)
if (this.options.onCancel && eventType !== 'done') {
this.options.onCancel()
}
this.cancelBtn.removeEventListener('click', this.cancel)
this.resetBtn.removeEventListener('click', this.reset)
this.confirmBtn.removeEventListener('click', this.done, true)
}
reset() {
this.scale = 1
this.rotate = 0
this.translateX = 0
this.translateY = 0
this.img1.style.transition = '0.3s'
this.img2.style.transition = '0.3s'
this.setTransform()
setTimeout(() => {
this.img1.style.transition = ''
this.img2.style.transition = ''
}, 300)
}
done() {
const zommRatio = this.options.width / this.frame2.clientWidth
const canvas = document.createElement('canvas')
canvas.width = this.options.width
canvas.height = this.options.height
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
let drawImageW
let drawImageH
if (this.options.ratio > this.originRatio) {
drawImageW = this.options.width
drawImageH = this.originH / (this.originW / this.options.width)
} else {
drawImageH = this.options.height
drawImageW = this.originW / (this.originH / this.options.height)
}
const point = { x: drawImageW / 2, y: drawImageH / 2 }
ctx.translate(this.translateX * zommRatio, this.translateY * zommRatio)
if (this.rotate !== 0) {
ctx.translate(point.x, point.y)
ctx.rotate((this.rotate * Math.PI) / 180)
ctx.translate(-point.x, -point.y)
}
if (this.scale !== 1) {
ctx.translate(point.x * (1 - this.scale), point.y * (1 - this.scale))
ctx.scale(this.scale, this.scale)
}
ctx.drawImage(this.img2, 0, 0, drawImageW, drawImageH)
if (this.options.onDone) {
switch (this.options.encode) {
case 'base64':
this.options.onDone(canvas.toDataURL(`image/${this.options.type}`, this.options.quality))
break
case 'blob':
canvas.toBlob(blob => {
this.options.onDone(blob)
}, `image/${this.options.type}`)
break
case 'file':
canvas.toBlob(blob => {
let file = new window.File([blob], this.options.name, { type: `image/${this.options.type}` })
this.options.onDone(file)
}, `image/${this.options.type}`)
break
default:
this.options.onDone(canvas.toDataURL(`image/${this.options.type}`, this.options.quality))
break
}
}
this.cancel('done')
}
}
export default Clipic