基于gitee 上的vue-asign插件改造肥矢。
修改部分:
1.使用setup 語法糖替換vue2 的選項式寫法塞琼。
2.使用偏移量 offsetX,offsetY替換原來的獲取筆尖位置的方法,解決在iframe 上使用的時候筆尖位置獲取不正確的問題
3.添加base64 回顯到畫板的方法
<template>
<canvas ref="vueSign" :style="{ background: props.bgColor }" @mousedown="mousedown" @mousemove="mousemove"
@mouseup="mouseup" @mouseleave="mouseup" @touchstart.stop="touchDown" @touchmove.stop="touchMove"
@touchend.stop="touchUp" @touchcancel.stop="touchUp" />
</template>
<script setup>
// 基于vue-asign改造, 主要是使用組合式語法替換選項式語法愧膀,同時解決筆尖偏移的問題
import {
ref,
reactive,
onMounted,
onUnmounted,
createApp,
computed, watch
} from "vue";
// ======================================組件屬性
const props = defineProps({
width: { type: Number, default: 600 },
height: { type: Number, default: 300 },
bgColor: { type: String, default: '' },
lineWidth: { type: Number, default: 4 },
lineColor: { type: String, default: '#000000' },
gapLeft: { type: Number, default: 5 },
gapRight: { type: Number, default: 5 },
gapTop: { type: Number, default: 5 },
gapBottom: { type: Number, default: 5 },
format: { type: String, default: '' },
quality: { type: String, default: '0.92' },
direction: { type: Number, default: 0 },
isCrop: { type: Boolean, default: true }
})
// =========================全局參數(shù)
let sratio = 1, ctx = null, resImg = '', isMove = false, lastX = 0, lastY = 0, offset = null;
const vueSign = ref()
const fillbg = computed(() => {
return props.bgColor ? props.bgColor : 'rgba(255,255,255,0)'
})
// 初始化
function initCanvas() {
const ratio = props.height / props.width
ctx = vueSign.value.getContext('2d', { willReadFrequently: true })
vueSign.value.height = props.height
vueSign.value.width = props.width
vueSign.value.style.width = props.width > window.innerWidth ? window.innerWidth + 'px' : props.width + 'px'
const realw = parseFloat(window.getComputedStyle(vueSign.value).width)
vueSign.value.style.height = ratio * realw + 'px'
vueSign.value.style.background = fillbg.value
ctx.scale(1 * sratio, 1 * sratio)
sratio = realw / props.width
ctx.scale(1 / sratio, 1 / sratio)
}
function mousedown(e) {
isMove = true
drawLine(e.offsetX, e.offsetY, false)
}
function mousemove(e) {
if (isMove) {
drawLine(e.offsetX, e.offsetY, true)
}
}
function mouseup(e) {
isMove = false
}
function touchDown(e) {
isMove = true
drawLine(
e.changedTouches[0].clientX - offset.left,
e.changedTouches[0].clientY - offset.top,
false
)
}
function touchMove(e) {
if (isMove) {
drawLine(
e.changedTouches[0].clientX - offset.left,
e.changedTouches[0].clientY - offset.top,
true
)
}
}
function touchUp(e) {
isMove = false
}
//畫線
function drawLine(x, y, isT) {
if (isT) {
ctx.beginPath()
ctx.lineWidth = props.lineWidth //設(shè)置線寬狀態(tài)
ctx.strokeStyle = props.lineColor //設(shè)置線的顏色狀態(tài)
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.moveTo(lastX, lastY)
ctx.lineTo(x, y)
ctx.stroke()
ctx.closePath()
}
// 每次移動都要更新坐標(biāo)位置
lastX = x
lastY = y
}
//清空畫圖
function clearCanvas() {
ctx.beginPath()
ctx.clearRect(0, 0, props.width, props.height)
ctx.closePath() //可加入拦键,可不加入
}
//線條粗細(xì)
function lineCrude() {
linWidthVal = selWidth[activeIndex].value
}
//改變顏色
function setColor() {
let activeIndex = selColor.selectedIndex
colorVal = selColor[activeIndex].value
}
//保存圖片
function createImg() {
return new Promise((resolve) => {
const resImgData = ctx.getImageData(0, 0, vueSign.value.width, vueSign.value.height)
const crop_area = getImgArea(resImgData.data)
const crop_canvas = document.createElement('canvas')
const crop_ctx = crop_canvas.getContext('2d')
crop_canvas.width = crop_area[2] - crop_area[0]
crop_canvas.height = crop_area[3] - crop_area[1]
const crop_imgData = ctx.getImageData(...crop_area)
crop_ctx.globalCompositeOperation = 'destination-over'
crop_ctx.putImageData(crop_imgData, 0, 0)
crop_ctx.fillStyle = fillbg.value
crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height)
let imgType = 'image/' + props.format
let resImg = crop_canvas.toDataURL(imgType, props.quality)
if (!props.isCrop) {
const ssign = vueSign.value
ctx.globalCompositeOperation = "destination-over"
ctx.fillStyle = fillbg.value
ctx.fillRect(0, 0, ssign.width, ssign.height)
resImg = ssign.toDataURL(imgType, props.quality)
ctx.clearRect(0, 0, ssign.width, ssign.height)
ctx.putImageData(resImgData, 0, 0)
ctx.globalCompositeOperation = "source-over"
}
if (props.direction > 0 && props.direction % 90 == 0) {
rotateBase64Img(resImg, props.direction, imgType).then(res => {
resolve(res)
})
} else {
resolve(resImg)
}
})
}
// 獲取圖片區(qū)域
function getImgArea(imgData) {
// const vueSign = vueSign.value
let left = vueSign.value.width,
top = vueSign.value.height,
right = 0,
bottom = 0
for (let i = 0; i < vueSign.value.width; i++) {
for (let j = 0; j < vueSign.value.height; j++) {
let k = (i + vueSign.value.width * j) * 4
if (imgData[k] > 0 || imgData[k + 1] > 0 || imgData[k + 2] || imgData[k + 3] > 0) {
bottom = Math.max(j, bottom)
right = Math.max(i, right)
top = Math.min(j, top)
left = Math.min(i, left)
}
}
}
left++
right++
top++
bottom++
const data = [
left - props.gapLeft,
top - props.gapTop,
right + props.gapRight,
bottom + props.gapBottom
]
return data
}
// 將base64圖片轉(zhuǎn)個角度并生成新的base64
function rotateBase64Img(src, edg, imgType) {
return new Promise((resolve) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
let imgW, imgH, size// canvas初始大小
if (edg % 90 != 0) {
console.error('旋轉(zhuǎn)角度必須是90的倍數(shù)!')
}
const quadrant = (edg / 90) % 4 // 旋轉(zhuǎn)象限
const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 } // 裁剪坐標(biāo)
const image = new Image()
image.crossOrigin = 'anonymous'
image.src = src
image.onload = function () {
imgW = image.width
imgH = image.height
size = imgW > imgH ? imgW : imgH
canvas.width = size * 2
canvas.height = size * 2
switch (quadrant) {
case 0:
cutCoor.sx = size
cutCoor.sy = size
cutCoor.ex = size + imgW
cutCoor.ey = size + imgH
break
case 1:
cutCoor.sx = size - imgH
cutCoor.sy = size
cutCoor.ex = size
cutCoor.ey = size + imgW
break
case 2:
cutCoor.sx = size - imgW
cutCoor.sy = size - imgH
cutCoor.ex = size
cutCoor.ey = size
break
case 3:
cutCoor.sx = size
cutCoor.sy = size - imgW
cutCoor.ex = size + imgH
cutCoor.ey = size + imgW
break
}
ctx.translate(size, size)
ctx.rotate(edg * Math.PI / 180)
ctx.drawImage(image, 0, 0)
var imgData = ctx.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey)
if (quadrant % 2 == 0) {
canvas.width = imgW
canvas.height = imgH
} else {
canvas.width = imgH
canvas.height = imgW
}
ctx.putImageData(imgData, 0, 0)
// 獲取旋轉(zhuǎn)后的base64圖片
resolve(canvas.toDataURL(imgType, this.quality))
}
})
}
// 把base64 顯示到畫板上
function setBase64toCanvas(bs) {
const img = new Image()
img.src = bs
img.onload = function() {
ctx.drawImage(img, 10,10)
}
}
defineExpose({ clearCanvas, createImg,setBase64toCanvas })
onMounted(() => {
initCanvas()
})
</script>
<style scoped>
canvas {
max-width: 100%;
display: block;
}
</style>