往期文章
本文将讲解通过 canvas 实现写字的效果
效果
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>学写一个字</title>
<style>
body {
overflow-x: hidden;
}
#canvas {
display: block;
margin: 25px auto 0;
}
#controller {
width: 100%;
padding: 10px;
margin: 0 auto;
}
#controller .op_btn {
float: right;
margin: 10px 0 0 10px;
border: 2px solid #aaa;
width: 40px;
height: 20px;
line-height: 20px;
font-size: 14px;
text-align: center;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
cursor: pointer;
background: #fff;
font-weight: bold;
font-family: Miscrosoft Yahei, Arial;
*zoom: 1;
margin-right: 3%;
}
#controller .op_btn:before,
#controller .op_btn:after {
display: table;
content: '';
}
#controller .op_btn:after {
clear: both;
}
#controller .op_btn:hover {
background: #def;
}
#controller .color_btn {
float: left;
margin: 10px 10px 0 0;
border: 5px solid #fff;
width: 20px;
height: 20px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
cursor: pointer;
}
#controller .color_btn:hover {
border: 5px solid violet;
}
#controller .color_btn_selected {
border: 5px solid blueviolet;
}
#controller #black_btn {
background: black;
}
#controller #blue_btn {
background: blue;
}
#controller #green_btn {
background: green;
}
#controller #red_btn {
background: red;
}
#controller #orange_btn {
background: orange;
}
#controller #yellow_btn {
background: yellow;
}
</style>
</head>
<body>
<canvas id="canvas">该浏览器不支持canvas</canvas>
<div id="controller">
<div id="black_btn" class="color_btn color_btn_selected"></div>
<div id="blue_btn" class="color_btn"></div>
<div id="green_btn" class="color_btn"></div>
<div id="red_btn" class="color_btn"></div>
<div id="orange_btn" class="color_btn"></div>
<div id="yellow_btn" class="color_btn"></div>
<div id="clear_btn" class="op_btn">清 除</div>
</div>
<script>
class Handwrite {
constructor(el) {
this.canvas = el;
this.ctx = this.canvas.getContext('2d');
this.canvasWidth = Math.min(800, document.body.clientWidth - 20);
this.canvasHeight = this.canvasWidth;
this.strokeColor = 'black';
this.isMouseDown = false;
this.lastLoc = { x: 0, y: 0 }; //上一次鼠标挪动的位置
this.lastTimeStmp = 0; // 上一次时间戳
this.lastLineWidth = -1; // 上一次的线宽
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
this._drawGrid();
this._initEvent(el);
}
// 画面板及米字格
_drawGrid() {
this.ctx.save();
//画一个矩形框
this.ctx.strokeStyle = 'red';
this.ctx.beginPath();
this.ctx.moveTo(3, 3);
this.ctx.lineTo(this.canvasWidth - 3, 3);
this.ctx.lineTo(this.canvasWidth - 3, this.canvasHeight - 3);
this.ctx.lineTo(3, this.canvasHeight - 3);
this.ctx.closePath();
this.ctx.lineWidth = 6;
this.ctx.stroke();
//画一个米字格
this.ctx.beginPath();
this.ctx.setLineDash([5, 5]); //参数:第一个是虚线的长度,第二个是两虚线段的间隔长度
this.ctx.moveTo(0, 0);
this.ctx.lineTo(this.canvasWidth, this.canvasHeight);
this.ctx.moveTo(this.canvasWidth, 0);
this.ctx.lineTo(0, this.canvasHeight);
this.ctx.moveTo(this.canvasWidth / 2, 0);
this.ctx.lineTo(this.canvasWidth / 2, this.canvasHeight);
this.ctx.moveTo(0, this.canvasHeight / 2);
this.ctx.lineTo(this.canvasWidth, this.canvasHeight / 2);
this.ctx.lineWidth = 1;
this.ctx.stroke();
this.ctx.restore();
}
// 将鼠标位置转换成在canvas中的位置
_windowToCanvas(x, y) {
const { top, left } = this.canvas.getBoundingClientRect() || this.canvas.getClientRect();
return {
x: Math.round(x - left),
y: Math.round(y - top)
};
}
// 计算两点之间的直线距离: (x*x+ y*y)开平方 */
_calcDistance(loc1, loc2) {
return Math.sqrt((loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2.y));
}
/**
* 计算笔画的粗细
* 停留时间越长,笔画越粗
* @param {[type]} t 时间
* @param {[type]} s 距离
*/
_calcLineWidth(t, s) {
const v = s / t;
let res;
var line = this.lastLineWidth || -1;
if (v < 0.1) {
res = 15;//最粗为15
} else if (v > 10) {
res = 1;//最细为1
} else {
// 1 v 15
// res ------ | -------
// v |---------|
// 0.1 10
res = 15 - (v - 0.1) / (10 - 0.1) * (15 - 5);
}
if (line === -1) {//第一次
return res;
} else {//不至于前后变化太大
return (line * 2 / 3 + res / 3);
}
}
_draw(curLoc, lineWidth) {
this.ctx.beginPath();
this.ctx.moveTo(this.lastLoc.x, this.lastLoc.y);
this.ctx.lineTo(curLoc.x, curLoc.y);
this.ctx.strokeStyle = this.strokeColor;
this.ctx.lineWidth = lineWidth;
this.ctx.lineCap = 'round';//解决毛边问题
this.ctx.lineJoin = 'round';//效果更佳平滑
this.ctx.stroke();
}
_beginStroke(x, y) {
this.isMouseDown = true;
this.lastLoc = this._windowToCanvas(x, y);
this.lastTimeStmp = new Date().getTime();
}
_endStroke() {
this.isMouseDown = false;
}
_moveStroke(ex, ey) {
var curLoc = this._windowToCanvas(ex, ey);
var curTimeStmp = new Date().getTime();
/*通过两点距离和时间实现笔画的粗细*/
var s = this._calcDistance(curLoc, this.lastLoc);
var t = curTimeStmp - this.lastTimeStmp;
var lineWidth = this._calcLineWidth(t, s);
this._draw(curLoc, lineWidth);
this.lastLoc = curLoc;
this.lastTimeStmp = curTimeStmp;
this.lastLineWidth = lineWidth;
}
_initEvent(el) {
const me = this;
/*pc*/
el.addEventListener('mousedown', (e) => {
e.preventDefault();
me._beginStroke(e.clientX, e.clientY);
}, false);
el.addEventListener('mouseup', (e) => {
e.preventDefault();
me._endStroke();
}, false);
el.addEventListener('mouseout', (e) => {
e.preventDefault();
me._endStroke();
}, false);
el.addEventListener('mousemove', (e) => {
e.preventDefault();
if (me.isMouseDown) {
me._moveStroke(e.clientX, e.clientY);
}
}, false);
/*mobile*/
el.addEventListener('touchstart', (e) => {
e.preventDefault();
const { pageX, pageY } = e.touches[0];//不需要多点触控
me._beginStroke(pageX, pageY);
}, false);
el.addEventListener('touchend', (e) => {
e.preventDefault();
me._endStroke();
}, false);
el.addEventListener('touchmove', (e) => {
e.preventDefault();
if (me.isMouseDown) {
const { pageX, pageY } = e.touches[0];
me._moveStroke(pageX, pageY);
}
}, false);
}
clear() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this._drawGrid();
}
selColor(color) {
this.strokeColor = color;
}
}
</script>
<script>
const canvas = document.getElementById('canvas');
const controller = document.getElementById('controller');
const hand = new Handwrite(canvas);
controller.addEventListener('click', e => {
const target = e.target;
const id = target.id;
if (id === 'controller') {
return;
}
if (id === 'clear_btn') {
hand.clear();
} else {
const color = getComputedStyle(target, null).getPropertyValue('background-color');
color && hand.selColor(color);
const hasSelected = document.querySelector('.color_btn_selected');
if (hasSelected.id !== id) {
hasSelected.classList.remove('color_btn_selected')
target.classList.add('color_btn_selected');
}
}
}, false);
</script>
</body>
</html>