前端小工具:CanvasDraw签名工具
前端小工具:CanvasDraw签名工具
移动端签名提示函
1、安装
npm install react-canvas-draw --save or yarn add react-canvas-draw
支持导出图片,文件数据流,
移动端横屏显示,
用于react项目
贴代码,大家都喜欢的。
import { Modal } from 'antd-mobile';
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import CanvasDraw from 'react-canvas-draw';
import style from './style';
class CanvasDraws extends PureComponent {
constructor(props) {
super(props);
this.signatureRef = React.createRef();
this.canvasRef = React.createRef();
this.placeholderRef = React.createRef();
this.state = {
visible: false,
imgUrl: '',
// 是否已经签名
isAutograph: false,
// 比例是否旋转
isScaleRotate: true,
signatureWidth: 300,
signatureHeight: 500,
};
}
componentDidMount() {
this.setWidthHeight();
window.addEventListener('resize', () => {
this.setWidthHeight();
this.setPlaceholderView();
});
}
// 设置宽高
setWidthHeight = () => {
const width = document.documentElement.clientWidth;
const height = document.documentElement.clientHeight;
// 如果宽度大于高度,则不旋转签名比例
if (width > height) {
this.setState({
isScaleRotate: false,
signatureWidth: width,
signatureHeight: height - 64,
});
} else {
this.setState({
isScaleRotate: true,
signatureWidth: width - 64,
signatureHeight: height,
});
}
};
// 显示签名函
showCanvasDraws = () => {
this.setState({
visible: true,
});
};
// 关闭签名提示函
handleCloseVisible = () => {
this.setState({
isAutograph: false,
visible: false,
});
};
handleClear = () => {
this.setPlaceholderView();
this.canvasRef.current.clear();
this.setState({
isAutograph: false,
});
};
handleConfirm = async () => {
const { onComplete } = this.props;
// const imgUrl = this.canvasRef.current.canvas.drawing.toDataURL('image/png');
this.canvasRef.current.canvas.drawing.toBlob(async blob => {
const blobRotate = await this.rotateBlob(blob);
const blobUrl = window.URL.createObjectURL(blobRotate);
this.setState({
imgUrl: blobUrl,
});
this.handleCloseVisible();
onComplete(blobRotate);
});
};
// 设置占位符显示隐藏功能
setPlaceholderView(view = 'block') {
if (this.placeholderRef.current && this.placeholderRef.current.style) {
this.placeholderRef.current.style.display = view;
}
}
handleIsSignState = () => {
this.setPlaceholderView('none');
this.setState({
isAutograph: true,
});
};
// 旋转图片
rotateBlob = (data = '') => {
const boldUrl = window.URL.createObjectURL(data);
const { isScaleRotate } = this.state;
//传入需要旋转的base64图片
return new Promise(resolve => {
const imgView = new Image();
imgView.src = boldUrl;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 };
imgView.onload = () => {
const imgW = imgView.width;
const imgH = imgView.height;
canvas.width = imgH;
canvas.height = imgW;
canvas.fillStyle = '#fff';
let imgData = null;
// 如果比例旋转变化,则旋转图片,否则默认返回
if (isScaleRotate) {
const size = imgH;
cutCoor.sx = size;
cutCoor.sy = size - imgW;
cutCoor.ex = size + imgH;
cutCoor.ey = size + imgW;
context.translate(size, size);
// 旋转图片
context.rotate((Math.PI / 2) * 3);
context.drawImage(imgView, 0, 0);
imgData = context.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
} else {
context.drawImage(imgView, 0, 0);
imgData = context.getImageData(0, 0, imgW, imgH);
}
for (let i = 0; i < imgData.data.length; i += 4) {
// 当该像素是透明的,则设置成白色
if (imgData.data[i + 3] === 0) {
imgData.data[i] = 255;
imgData.data[i + 1] = 255;
imgData.data[i + 2] = 255;
imgData.data[i + 3] = 255;
}
}
context.putImageData(imgData, 0, 0);
canvas.toBlob(blob => {
resolve(blob);
}, 'image/jpeg');
};
});
};
// 签名模板
renderSignatureRegion() {
const { visible, isScaleRotate, signatureWidth, signatureHeight, isAutograph } = this.state;
return (
<style.CanvasDraw>
<div className={isScaleRotate ? 'rotate' : 'normal'}>
<div
className="signature-regin"
ref={this.signatureRef}
style={{ width: signatureWidth }}
>
{visible && (
<CanvasDraw
ref={this.canvasRef}
loadTimeOffset={5}
brushRadius={2}
lazyRadius={0}
brushColor="#444"
backgroundColor="#fff"
catenaryColor="#0a0302"
gridColor="transparent"
hideInterface
canvasWidth={signatureWidth}
canvasHeight={signatureHeight}
onChange={() => this.handleIsSignState()}
/>
)}
<div className="signature-placeholder" ref={this.placeholderRef}>
<p>签名区域</p>
</div>
<button
aria-label="Close"
className="am-modal-close close-signature"
onClick={this.handleCloseVisible}
>
✖
</button>
</div>
<div className="signature-btn">
<div className="signature-tips">
<a>
请保持手机<b>横屏</b>,并按照从左到右的方向居中签名
</a>
</div>
<div className="signature-btn-container">
<button onClick={() => this.handleClear()} className="clear-signature">
清除签名
</button>
<button
onClick={() => this.handleConfirm()}
className={isAutograph ? 'submit-signature' : 'submit-signature disabled'}
disabled={!isAutograph}
>
保存签名
</button>
</div>
</div>
</div>
</style.CanvasDraw>
);
}
render() {
const { visible, imgUrl } = this.state;
const { agreedraw } = this.props;
return (
<style.Signature>
<div className="signature default">
<div onClick={this.showCanvasDraws}>
<div className="signature-block">
{agreedraw ? (
<img className="preview" src={imgUrl}></img>
) : (
<div className="placeholder">
<p>点击此处签名</p>
</div>
)}
</div>
</div>
{/* 签名区域 */}
<Modal popup className="signature-region-wrap" animationType="slide-up" visible={visible}>
{this.renderSignatureRegion()}
</Modal>
</div>
</style.Signature>
);
}
}
// 类型检查
CanvasDraws.propTypes = {
agreedraw: PropTypes.bool,
onComplete: PropTypes.func,
};
// 默认值
CanvasDraws.defaultProps = {
// 是否签名
agreedraw: false,
// 签名完成
onComplete: () => {},
};
export default CanvasDraws;
css:只能参考哇
import { pxToRem as rem } from 'lib/style.lib';
import styled from 'styled-components';
export default {
Signature: styled.div`
.signature {
position: relative;
border-radius: 2px;
text-align: center;
height: ${rem(276)};
background: #fff;
display: flex;
align-items: center;
justify-content: center;
i &.default {
border-radius: ${rem(4)};
box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.05);
}
&.default {
background: #eaebee;
}
&.result {
.preview {
max-height: ${rem(280)};
}
/* &:after {
@include border2(full, #e0e0e0, rem(4px));
} */
}
.signature-block {
width: 100%;
}
.preview {
display: block;
width: 100%;
height: ${rem(276)};
box-sizing: border-box;
border: 1px solid #eaebee;
}
.placeholder {
color: #d1d1d1;
i {
font-size: ${rem(38)};
svg {
display: block;
margin: 0 auto;
}
}
p {
font-size: ${rem(44)};
line-height: 1.1;
color: #949ca8;
}
}
.tip-signature {
line-height: 1.1;
font-size: ${rem(28)};
}
}
`,
CanvasDraw: styled.div`
// 弹出框
.signature-regin {
position: relative;
left: 0;
}
.close-signature {
position: absolute;
right: 15px;
top: 0;
padding: 8px 0;
width: 30px;
height: 30px;
color: #999;
font-size: ${rem(40)};
.am-modal-close-x {
color: #999 !important;
font-size: 18px !important;
&:after {
display: none !important;
}
svg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
.am-modal-close {
top: 15px;
}
.signature-placeholder {
position: absolute;
top: 50%;
left: -24px;
width: 100%;
transform: translateY(-55%);
pointer-events: none;
i {
color: #d1d1d1;
font-size: ${rem(56)};
}
p {
margin-top: 5px;
font-size: ${rem(140)};
color: #e8eaed;
}
}
.signature-btn {
display: flex;
background: rgba(13, 110, 255, 0.1);
padding: 12px 32px 12px 24px;
justify-content: space-between;
align-items: center;
a {
font-size: 15px;
color: #1272ff;
}
button {
width: 80px;
height: 38px;
border: 1px solid rgba(13, 110, 255);
border-radius: 19px;
}
button:nth-of-type(1) {
background-color: rgba(90, 154, 255, 0.1);
color: #1272ff;
margin-right: 16px;
}
button:nth-of-type(2) {
background-color: #1272ff;
color: #fff;
&.disabled {
background-color: #93afd8;
}
}
}
.rotate {
.signature-regin {
left: 64px;
}
.signature-placeholder {
transform: translateY(-55%) rotate(90deg);
}
.close-signature {
top: auto !important;
bottom: 15px !important;
}
.signature-btn {
position: absolute;
left: 0;
top: 0;
width: 64px;
height: 100%;
padding: 15px 10px;
flex-direction: column;
background: rgba(13, 110, 255, 0.1);
.signature-tips {
display: flex;
transform: rotate(90deg) translateX(50%);
width: 500px;
}
.signature-btn-container {
padding-right: ${rem(60)};
display: flex;
transform: rotate(90deg) translateX(-50%);
button {
position: relative;
}
}
}
}
`,
};
我没有才华,只能是贴代码了。
大家加油了。
没有终点,没有彼岸,坚持就好,愿岁月如初

浙公网安备 33010602011771号