前端小工具: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={{ 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 { 100%; } .preview { display: block; 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; 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; 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 { 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; 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%); 500px; } .signature-btn-container { padding-right: ${rem(60)}; display: flex; transform: rotate(90deg) translateX(-50%); button { position: relative; } } } } `, };
我没有才华,只能是贴代码了。
大家加油了。