})
this.rc.rectangle(225, 270, 80, 30, {
fill: ‘red’,
fillweight: 5
})
this.rc.line(200, 150, 150, 80, { roughness: 5 })
this.rc.line(300, 150, 350, 80, { roughness: 2 })
效果如下:是不是有点蠢萌,本文的主要内容是带大家手动实现上面的图形,最终效果预览:lxqnsys.com/#/demo/hand…[2]。话不多说,代码见。
线段
–
万物基于线段,所以先来看线段怎么画,仔细看上图会发现手绘版线段其实是用两根弯曲的线段组成的,曲线可以使用贝塞尔曲线来画,这里使用三次贝塞尔曲线,那么剩下的问题就是求起点、终点、两个控制点的坐标了。贝塞尔曲线可以在这个网站上尝试:cubic-bezier.com/[3]。首先一条线段的起点和终点我们都给它加一点随机值,随机值比如就在[-2,2]之间,也可以把这个范围和线段的长度关联起来,比如线段越长,随机值就越大。
// 直线变曲线
_line (x1, y1, x2, y2) {
let result = []
// 起始点
result[0] = x1 + this.random(-this.offset, this.offset)
result[1] = y1 + this.random(-this.offset, this.offset)
// 终点
result[2] = x2 + this.random(-this.offset, this.offset)
result[3] = y2 + this.random(-this.offset, this.offset)
}
接下来就是两个控制点,我们把控制点限定在线段所在的矩形内:
_line (x1, y1, x2, y2) {
let result = []
// 起始点
// …
// 终点
// …
// 两个控制点
let xo = x2 - x1
let yo = y2 - y1
let randomFn = (x) => {
return x > 0 ? this.random(0, x) : this.random(x, 0)
}
result[4] = x1 + randomFn(xo)
result[5] = y1 + randomFn(yo)
result[6] = x1 + randomFn(xo)
result[7] = y1 + randomFn(yo)
return result
}
然后把上面生成的曲线绘制出来:
// 绘制手绘线段
line (x1, y1, x2, y2) {
this.drawDoubleLine(x1, y1, x2, y2)
}
// 绘制两条曲线
drawDoubleLine (x1, y1, x2, y2) {
// 绘制生成的两条曲线
let line1 = this._line(x1, y1, x2, y2)
let line2 = this._line(x1, y1, x2, y2)
this.drawLine(line1)
this.drawLine(line2)
}
// 绘制单条曲线
drawLine (line) {
this.ctx.beginPath()
this.ctx.moveTo(line[0], line[1])
// bezierCurveTo方法前两个点为控制点,第三个点为结束点
this.ctx.bezierCurveTo(line[4], line[5], line[6], line[7], line[2], line[3])
this.ctx.strokeStyle = ‘#000’
this.ctx.stroke()
}
效果如下:但是多试几次就会发现偏离太远、弯曲程度过大:完全不像一个手正常的人能画出来的,去上面的贝塞尔曲线网站上试几次会发现两个控制点离线段越近,曲线弯曲程度越小:所以我们要找线段附近的点作为控制点,首先随机一个横坐标点,然后可以计算出线段上该横坐标对应的纵坐标点,把该纵坐标点加减一点随机值即可。
_line (x1, y1, x2, y2) {
let result = []
// …
// 两个控制点
let c1 = this.getNearRandomPoint(x1, y1, x2, y2)
let c2 = this.getNearRandomPoint(x1, y1, x2, y2)
result[4] = c1[0]
result[5] = c1[1]
result[6] = c2[0]
result[7] = c2[1]
return result
}
// 计算两个点连成的线段上附近的一个随机点
getNearRandomPoint (x1, y1, x2, y2) {
let xo, yo, rx, ry
// 垂直x轴的线段特殊处理
if (x1 === x2) {
yo = y2 - y1
rx = x1 + this.random(-2, 2)// 在横坐标附近找一个随机点
ry = y1 + yo * this.random(0, 1)// 在线段上找一个随机点
return [rx, ry]
}
xo = x2 - x1
rx = x1 + xo * this.random(0, 1)// 找一个随机的横坐标
ry = ((rx - x1) * (y2 - y1)) / (x2 - x1) + y1// 通过两点式求出直线方程
ry += this.random(-2, 2)// 纵坐标加一点随机值
return [rx, ry]
}
看一下效果:当然和Rough.js
比起来还是不够好,有兴趣的可以自行去看一下源码,反正笔者是看不懂,控制变量太多,还没有注释。
多边形&矩形
多边形就是把多个点首尾相连起来,遍历顶点调用绘制线段的方法即可:
// 绘制手绘多边形
polygon (points = [], opt = {}) {
if (points.length < 3) {
return
}
let len = points.length
for (let i = 0; i < len - 1; i++) {
this.line(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1])
}
// 首尾相连
this.line(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1])
}