成品展示

组件使用调用方式
// 引入组件
import Signature from "components/Signature";
// 调用组件
<SignatureBtn
width={"1030px"}
btnText="手签"
onConfirm={this.handleSignature}
></SignatureBtn>
实现思路
- 我使用的技术栈是
React
同时使用的是类组件,面向对象编程。
- 考虑到需要控制鼠标在容器里写字,所以需要使用
canvas
技术。
- 具体实现逻辑:
a. 页面有一个"手签的按钮",点击按钮显示一个弹出框,点击确认或取消关闭弹出框。
b. 弹出框内展示手写签名的组件。
c. 签名组件包括:签字,清空重新,生成图片等操作。
d. 核心签名逻辑即:控制鼠标的
mousedown
、mouseup
、mousemove
事件。
- 组件的拆分:
根据功能拆为两个部分。
外层组件:一个按钮和一个弹出框,通过按钮控制弹出框的显示及隐藏。
内层组件:手写签字核心部分。
a. 签字逻辑。
b. 清空逻辑。
c. 回退上一步逻辑。
d.
canvas
转换为 base64
字符串逻辑。
e. ocr
识别。 这部分后续增加。
核心代码示例
// 手写签字核心类
class Signature extends Component {
constructor(props) {
super(props);
this.state = {
isDrawing: false,
drawingSteps: [], // 绘图步骤; 用于回退功能
lastX: 0,
lastY: 0,
width: 990,
height: 300,
dataURL: "", //生成的图片的Base64字符串
};
}
canvasRef = React.createRef();
componentDidMount() {
const canvas = this.canvasRef.current;
const context = canvas.getContext("2d");
context.strokeStyle = "blue";
context.lineWidth = 2;
// 注册事件
canvas.addEventListener("mousedown", this.handleMouseDown);
canvas.addEventListener("mouseup", this.handleMouseUp);
canvas.addEventListener("mousemove", this.handleMouseMove);
}
componentWillUnmount() {
const canvas = this.canvasRef.current;
// 销毁事件
canvas.removeEventListener("mousedown", this.handleMouseDown);
canvas.removeEventListener("mouseup", this.handleMouseUp);
canvas.removeEventListener("mousemove", this.handleMouseMove);
}
//鼠标左键按下
handleMouseDown = (e) => {
this.setState({
isDrawing: true,
lastX: e.offsetX,
lastY: e.offsetY,
});
};
// 鼠标左键释放
handleMouseUp = () => {
this.setState({
isDrawing: false,
});
};
// 鼠标按下拖动
handleMouseMove = (e) => {
let { isDrawing, lastX, lastY, drawingSteps } = this.state;
if (!isDrawing) return;
const canvas = this.canvasRef.current;
const context = canvas.getContext("2d");
const newX = e.offsetX;
const newY = e.offsetY;
context.beginPath();
context.moveTo(lastX, lastY);
context.lineTo(newX, newY);
context.stroke();
this.setState({
lastX: newX,
lastY: newY,
drawingSteps: [...drawingSteps, { lastX, lastY, newX, newY }],
});
};
// canvas 转换为 base64字符串
generateImage = () => {
const canvas = this.canvasRef.current;
const dataURL = canvas.toDataURL();
debugger;
this.setState({
dataURL,
});
return dataURL;
};
// 清空操作
clearAll = () => {
const canvas = this.canvasRef.current;
const context = canvas.getContext("2d");
context.clearRect(0, 0, 990, 300);
this.setState({
dataURL: "",
drawingSteps: [],
});
};
// 回退操作
undoStep = () => {
let { drawingSteps } = this.state;
const canvas = this.canvasRef.current;
const context = canvas.getContext("2d");
if (drawingSteps.length > 0) {
const lastStep = drawingSteps[drawingSteps.length - 40];
context.clearRect(0, 0, 990, 300);
drawingSteps.slice(0, -1).forEach((step) => {
context.beginPath();
context.moveTo(step.lastX, step.lastY);
context.lineTo(step.newX, step.newY);
context.stroke();
});
this.setState({
drawingSteps: drawingSteps.slice(0, -1),
});
}
};
render() {
let { showBtn } = this.props;
let { dataURL, width, height } = this.state;
return (
<div>
<canvas
ref={this.canvasRef}
width={width}
height={height}
style={signtureStyle}
></canvas>
{!!dataURL && showImg && (
<img
style={previewStyle}
src={dataURL}
width={width / 2}
height={height / 2}
></img>
)}
{showBtn && (
<>
<button onClick={this.undoStep}>上一步</button>
<button onClick={this.clearAll}>重置</button>
<button onClick={this.generateImage}>生成图片</button>
</>
)}
</div>
);
}
}
思考
- 封装组件需要考虑独立性,可移植性,尽量减少对其他代码的依赖。
- 封装的可扩展性,一个组件不可能一次写好,适用所有场景,需要不断的改善,兼容不同场景。
- 逻辑封层,能拆分则拆分。这样利于扩展,代码可读性也强。