1
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
<script lang="ts">
|
||||
import { nextTick, ComponentInternalInstance } from "vue"
|
||||
import { px2dp, checkIsCssUnit, getUid } from "../../core/util/xCoreUtil.uts"
|
||||
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
|
||||
import { xConfig } from "../../config/xConfig.uts"
|
||||
type HUIZHI_TYPE = {
|
||||
width : string,
|
||||
height : string,
|
||||
backgroundColor : string,
|
||||
strokeColor : string,
|
||||
strokeWidth : number,
|
||||
}
|
||||
type EvtCustomDetail = {
|
||||
x : number,
|
||||
y : number,
|
||||
type : string
|
||||
}
|
||||
type PointVegByXsignBorad = {
|
||||
x : number,
|
||||
y : number,
|
||||
lineWidth:number
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @name 签名板 xSignBoard
|
||||
* @description 手写签名板,适合需要签字的场景
|
||||
* @page /pages/index/sign-board
|
||||
* @category 其它组件
|
||||
* @constant 平台兼容
|
||||
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ctx: null as null | CanvasRenderingContext2D,
|
||||
canvas: null as UniCanvasElement | null,
|
||||
isDrawer: false,
|
||||
id: "xSignBoard" + getUid(),
|
||||
ratio: 1,
|
||||
_x: 0,
|
||||
_y: 0,
|
||||
_left: 0,
|
||||
_top: 0,
|
||||
tid: 0,
|
||||
realWidth: 0,
|
||||
realHeight: 0,
|
||||
// 添加笔峰相关变量
|
||||
lastTime: 0,
|
||||
lastX: 0,
|
||||
lastY: 0,
|
||||
diffXY: 1, // 差异,比如两点之间小于1就不会存储。可减少点数提高 性能。
|
||||
ponits: [] as PointVegByXsignBorad[][],
|
||||
currentPath:[] as PointVegByXsignBorad[],
|
||||
step:0
|
||||
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* 宽度,可任意单位
|
||||
*/
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
/**
|
||||
* 高度,可任意单位
|
||||
*/
|
||||
height: {
|
||||
type: String,
|
||||
default: '150'
|
||||
},
|
||||
/**
|
||||
* 背景颜色
|
||||
*/
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'rgba(0,0,0,0)'
|
||||
},
|
||||
/**
|
||||
* 画笔颜色
|
||||
*/
|
||||
strokeColor: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
/**
|
||||
* 画笔粗细
|
||||
*/
|
||||
strokeWidth: {
|
||||
type: Number,
|
||||
default: 8
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_props() : HUIZHI_TYPE {
|
||||
|
||||
return {
|
||||
width: checkIsCssUnit(this.width, xConfig.unit),
|
||||
height: checkIsCssUnit(this.height, xConfig.unit),
|
||||
backgroundColor: getDefaultColor(this.backgroundColor),
|
||||
strokeColor: getDefaultColor(this.strokeColor),
|
||||
strokeWidth: this.strokeWidth,
|
||||
} as HUIZHI_TYPE
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
// #ifdef WEB||MP
|
||||
this.getNodes();
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
let t = this;
|
||||
this.tid = setTimeout(function () {
|
||||
t.getNodes();
|
||||
}, 120);
|
||||
// #endif
|
||||
|
||||
uni.$on('onResize', this.getNodes)
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearTimeout(this.tid)
|
||||
uni.$off('onResize', this.getNodes)
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
let t = this;
|
||||
let ratio = uni.getWindowInfo().pixelRatio;
|
||||
uni.createCanvasContextAsync({
|
||||
id: t.id as string,
|
||||
component: t,
|
||||
success(context) {
|
||||
let ctx:CanvasRenderingContext2D|null = context.getContext('2d')!;
|
||||
let canvas = ctx!.canvas!
|
||||
if (ctx == null) return;
|
||||
canvas.width = canvas.offsetWidth * ratio
|
||||
canvas.height = canvas.offsetHeight * ratio
|
||||
t.realWidth = canvas.width
|
||||
t.realHeight = canvas.height
|
||||
ctx.scale(ratio, ratio);
|
||||
t.canvas = canvas!;
|
||||
t.ctx = ctx!;
|
||||
ratio = 1;
|
||||
t.ratio = ratio;
|
||||
t.clear()
|
||||
},
|
||||
fail() {
|
||||
uni.showToast({ title: '错误', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
eventDips(evt : EvtCustomDetail) {
|
||||
if (evt.type == 'start') {
|
||||
this.isDrawer = true;
|
||||
this._x = evt.x - this._left
|
||||
this._y = evt.y - this._top
|
||||
// 初始化笔峰相关变量
|
||||
this.lastTime = Date.now()
|
||||
this.lastX = this._x
|
||||
this.lastY = this._y
|
||||
// 初始化新的路径组
|
||||
this.currentPath = [] as PointVegByXsignBorad[]
|
||||
// 存储第一个点
|
||||
this.currentPath.push({x: this._x, y: this._y,lineWidth:this._props.strokeWidth } as PointVegByXsignBorad)
|
||||
}
|
||||
if (evt.type == 'move') {
|
||||
if (this.isDrawer) {
|
||||
let ctx = this.ctx!
|
||||
let new_x = evt.x - this._left
|
||||
let new_y = evt.y - this._top
|
||||
|
||||
|
||||
let currentTime = Date.now()
|
||||
// 根据速度调整笔画粗细,速度越快笔画越细
|
||||
let width = this._props.strokeWidth
|
||||
|
||||
// #ifndef APP-HARMONY
|
||||
// 计算速度和笔画粗细
|
||||
|
||||
let deltaTime = currentTime - this.lastTime
|
||||
let distance = Math.sqrt(Math.pow(new_x - this.lastX, 2) + Math.pow(new_y - this.lastY, 2))
|
||||
let speed = distance / deltaTime
|
||||
width = Math.max(this._props.strokeWidth * (1 - speed * 0.5), this._props.strokeWidth * 0.4)
|
||||
|
||||
// #endif
|
||||
|
||||
// 判断是否需要存储当前点
|
||||
let lastStoredPoint = this.currentPath[this.currentPath.length - 1]
|
||||
let distanceFromLastStored = Math.sqrt(
|
||||
Math.pow(new_x - lastStoredPoint.x, 2) +
|
||||
Math.pow(new_y - lastStoredPoint.y, 2)
|
||||
)
|
||||
// 当距离大于阈值且当前路径点数小于最大限制时存储点
|
||||
if (distanceFromLastStored > this.diffXY && this.currentPath.length < 100) {
|
||||
this.currentPath.push({x: new_x, y: new_y,lineWidth:width} as PointVegByXsignBorad)
|
||||
}
|
||||
|
||||
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = this._props.strokeColor
|
||||
// #ifndef APP-HARMONY
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
// #endif
|
||||
ctx.lineWidth = width * this.ratio;
|
||||
ctx.moveTo(this._x * this.ratio, this._y * this.ratio);
|
||||
ctx.lineTo(new_x * this.ratio, new_y * this.ratio);
|
||||
ctx.stroke();
|
||||
|
||||
// 更新位置和时间
|
||||
this._x = new_x
|
||||
this._y = new_y
|
||||
this.lastTime = currentTime
|
||||
this.lastX = new_x
|
||||
this.lastY = new_y
|
||||
|
||||
ctx.closePath()
|
||||
}
|
||||
}
|
||||
if (evt.type == 'end') {
|
||||
this.isDrawer = false;
|
||||
this._x = evt.x - this._x
|
||||
this._y = evt.y - this._y
|
||||
// 将当前路径组添加到ponits数组中
|
||||
if (this.currentPath.length > 1) {
|
||||
this.ponits.push(this.currentPath)
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
mStart(evt : UniTouchEvent) {
|
||||
evt.stopPropagation()
|
||||
evt.preventDefault()
|
||||
|
||||
let t = this;
|
||||
let event = evt.changedTouches[0];
|
||||
uni.createSelectorQuery().in(t)
|
||||
.select(".canvas")
|
||||
.boundingClientRect().exec((ret) => {
|
||||
let nodeinfo = ret[0] as NodeInfo;
|
||||
t._left = nodeinfo.left!;
|
||||
t._top = nodeinfo.top!;
|
||||
// #ifdef WEB
|
||||
this._top += uni.getWindowInfo().windowTop;
|
||||
// #endif
|
||||
t.eventDips({ x: event.clientX, y: event.clientY, type: 'start' } as EvtCustomDetail)
|
||||
})
|
||||
},
|
||||
mMove(evt : UniTouchEvent) {
|
||||
if (!this.isDrawer) return
|
||||
evt.stopPropagation()
|
||||
evt.preventDefault()
|
||||
let event = evt.changedTouches[0];
|
||||
this.eventDips({ x: event.clientX, y: event.clientY, type: 'move' } as EvtCustomDetail)
|
||||
},
|
||||
mEnd(evt : UniTouchEvent) {
|
||||
let event = evt.changedTouches[0];
|
||||
this.eventDips({ x: event.clientX, y: event.clientY, type: 'end' } as EvtCustomDetail)
|
||||
},
|
||||
// #ifdef WEB
|
||||
mmStart(evt : UniMouseEvent) {
|
||||
this.eventDips({ x: evt.clientX, y: evt.clientY, type: 'start' } as EvtCustomDetail)
|
||||
|
||||
},
|
||||
mmMove(evt : UniMouseEvent) {
|
||||
if (!this.isDrawer) return
|
||||
evt.stopPropagation()
|
||||
evt.preventDefault()
|
||||
this.eventDips({ x: evt.clientX, y: evt.clientY, type: 'move' } as EvtCustomDetail)
|
||||
},
|
||||
mmEnd(evt : UniMouseEvent) {
|
||||
this.eventDips({ x: evt.clientX, y: evt.clientY, type: 'end' } as EvtCustomDetail)
|
||||
},
|
||||
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 获取当前的笔画数量
|
||||
* 这是个大概值,并非完全准确,但至少可以验证
|
||||
* 出用户写了多少笔,是否真的签名了。
|
||||
* @public
|
||||
*/
|
||||
getLineCount():number{
|
||||
return this.ponits.length;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空画布
|
||||
* @public
|
||||
*/
|
||||
clear() {
|
||||
let ctx = this.ctx!;
|
||||
let canvas = this.canvas!;
|
||||
this.ponits = [];
|
||||
this.step = 0
|
||||
ctx.fillStyle = 'rgba(0,0,0,0)'
|
||||
ctx.fillRect(0, 0, this.realWidth*this.ratio, this.realHeight*this.ratio)
|
||||
ctx.reset();
|
||||
let ratio = uni.getWindowInfo().pixelRatio;
|
||||
ctx.scale(ratio,ratio)
|
||||
},
|
||||
drawOffC(pos : PointVegByXsignBorad[][]) {
|
||||
let ctx = this.ctx!;
|
||||
let canvas = this.canvas!;
|
||||
|
||||
ctx.fillStyle = 'rgba(0,0,0,0)'
|
||||
ctx.fillRect(0, 0, this.realWidth, this.realHeight)
|
||||
// #ifdef MP||WEB
|
||||
ctx!.clearRect(0, 0, this.realWidth, this.realHeight)
|
||||
// #endif
|
||||
// #ifdef APP
|
||||
ctx.reset();
|
||||
// #endif
|
||||
|
||||
|
||||
ctx!.strokeStyle = this._props.strokeColor
|
||||
pos.forEach((el : PointVegByXsignBorad[]) => {
|
||||
|
||||
let p = el[0]
|
||||
let x0 = p.x
|
||||
let y0 = p.y
|
||||
ctx!.lineWidth = p.lineWidth * this.ratio;
|
||||
ctx!.beginPath();
|
||||
// ctx.moveTo(px2dp(x0), px2dp(y0));
|
||||
el.forEach((d : PointVegByXsignBorad) => {
|
||||
let x1 = d.x
|
||||
let y1 = d.y
|
||||
ctx!.moveTo(x0 * this.ratio, y0 * this.ratio);
|
||||
ctx!.lineTo(x1 * this.ratio, y1 * this.ratio);
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
// ctx.moveTo(px2dp(x0), px2dp(y0));
|
||||
ctx!.lineCap = 'round';
|
||||
ctx!.stroke();
|
||||
|
||||
})
|
||||
ctx!.closePath()
|
||||
})
|
||||
|
||||
},
|
||||
/**
|
||||
* 回退的步数,暂不开放
|
||||
* @param {number} n - 回退的步数
|
||||
*/
|
||||
back(n : number) {
|
||||
this.step += n
|
||||
if (this.ponits.length - this.step < 0 || this.step < 0) {
|
||||
uni.showToast({ title: "超出步骤数", icon: 'none' })
|
||||
} else {
|
||||
let newdata = this.ponits.slice(0, this.ponits.length - (this.step+1))
|
||||
this.drawOffC(newdata)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 前进的步数,暂不开放
|
||||
* @param {number} n - 前进的步数
|
||||
*/
|
||||
go(n : number) {
|
||||
this.step -= n
|
||||
if (this.step < 0) {
|
||||
uni.showToast({ title: "超出步骤数", icon: 'none' })
|
||||
} else {
|
||||
let newdata = this.ponits.slice(0, this.ponits.length - this.step)
|
||||
this.drawOffC(newdata)
|
||||
}
|
||||
|
||||
},
|
||||
getNodes() {
|
||||
uni.createSelectorQuery().in(this)
|
||||
.select(".canvas")
|
||||
.boundingClientRect().exec((ret) => {
|
||||
let nodeinfo = ret[0] as NodeInfo;
|
||||
this._left = nodeinfo.left!;
|
||||
this._top = nodeinfo.top!;
|
||||
// #ifdef WEB
|
||||
this._top += uni.getWindowInfo().windowTop;
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取图片数据
|
||||
* @public
|
||||
* @returns {Promise<UTSJSONObject>} - {src:图片路径,width:图片宽,height:图片高,error:错误信息}
|
||||
*/
|
||||
getImage() : Promise<UTSJSONObject> {
|
||||
let t = this;
|
||||
let canvas = this.canvas!;
|
||||
let width = this.canvas?.offsetWidth;
|
||||
let height = this.canvas?.offsetHeight
|
||||
return new Promise((res, rej) => {
|
||||
var imageData = t.canvas!.toDataURL('image/png');
|
||||
res({
|
||||
src: imageData,
|
||||
width: width,
|
||||
height: height,
|
||||
error: "成功"
|
||||
} as UTSJSONObject)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
||||
<canvas :id="id" ref="canvas" type="2d" class="canvas"
|
||||
|
||||
@touchstart="mStart"
|
||||
@touchend="mEnd"
|
||||
@touchmove.stop="mMove"
|
||||
<!-- #ifdef WEB -->
|
||||
@mousedown="mmStart"
|
||||
@mouseup="mmEnd"
|
||||
@mousemove.stop="mmMove"
|
||||
@mouseleave="mmEnd"
|
||||
<!-- #endif -->
|
||||
:style="{width:_props.width,height:_props.height}"></canvas>
|
||||
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user