为了让她学画画——熬夜用canvas实现了一个画板
前言
大家好,我是Fly, canvas真是个强大的东西,每天沉迷这个无法自拔, 可以做游戏,可以对图片处理,后面会给大家分享一篇,canvas实现两张图片找不同的功能, 听着是不是挺有意思的, 有点像游戏 找你妹,但是这都不是本篇文章想要表达的重点,读完今天这篇文章,你可以学到什么呢
- Canvas 实现一个简单的画版小工具
- Canvas 画出平滑的曲线, 这是本篇文章的重点
这时候有人问我她??, 我的心里没有她的,只有你们coder, 下面一起学习吧,预计阅读10分钟。
canvas实现一个画版小工具
因为也比较简单,我大概说下思路:
- 首先我对canvas 画布坚监听3个事件, 分别是mouseMove,mouseDown,mouseUp 三个事件, 同时创建了isDown 这个变量, 用来标记当前画图是不是开启
- 当我们按下鼠标 也就是mouseDown 事件, 表示开始画笔,有一个初始的点, 并把isDown 设置为true, 然后紧着呢开始移动, 可以确定直线的端点, 然后再把直线的端点设置为下一条直线的起始点, 不断地重复这个过程, mousueUp 将isDown 这个变量设置为false, 同时清空开始点和结束点
- 通过mouseMove事件不断采集鼠标经过的坐标点,当且仅当isDown 为true(即处于书写状态)时将当前的点通过canvas的LineTo方法与前面的点进行连接、绘制;
代码如下:
class board {
constructor() {
this.canvas = document.getElementById('canvas')
this.canvas.addEventListener('mousemove', this.move.bind(this))
this.canvas.addEventListener('mousedown', this.down.bind(this))
this.canvas.addEventListener('mouseup', this.up.bind(this))
this.ctx = this.canvas.getContext('2d')
this.startP = null
this.endP = null
this.isDown = false
this.setLineStyle()
}
setLineStyle() {
this.ctx.strokeStyle = 'red'
this.ctx.lineWidth = 1
this.ctx.lineJoin = 'round'
this.ctx.lineCap = 'round'
}
move(e) {
if (!this.isDown) {
return
}
this.endP = this.getPot(e)
this.drawLine()
this.startP = this.endP
}
down(e) {
this.isDown = true
this.startP = this.getPot(e)
}
getPot(e) {
return new Point2d(e.offsetX, e.offsetY)
}
drawLine() {
if (!this.startP || !this.endP) {
return
}
this.ctx.beginPath()
this.ctx.moveTo(this.startP.x, this.startP.y)
this.ctx.lineTo(this.endP.x, this.endP.y)
this.ctx.stroke()
this.ctx.closePath()
}
up(e) {
this.startP = null
this.endP = null
this.isDown = false
}
}
new board()
point2d是我自己写的一个2d点的一个类,不清楚的同学可以看我前几篇文章, 这里就不重复阐述了。我们看下gif:
细心的同学可能发现,画的线折线感比较强,出现这个本质的原因—— 就是我们画出的线其实是一个多段线polyline, 连接两个点之间的线是直线
如何画出平滑的曲线
想起曲线,就不得不提到贝塞尔曲线了,我之前的文章有系统的介绍过贝塞尔曲线,以及贝塞尔曲线方程的推导过程—— 传送门
canvas 肯定是支持贝塞尔曲线的quadraticCurveTo(cp1x, cp1y, x, y) , 主要是一个起始点, 一个终点,一个控制点。 其实这里可以用一个巧妙的算法去解决这样的问题。
获取二阶贝塞尔曲线信息的算法
假设我们在鼠标移动的过程中有A、B、C、D、E、F、G、这6个点。如何画出平滑的曲线呢, 我们取B点和C点的中点B1 作为第一条贝塞尔曲线的终点,B点作为控制点。如图:
接下来呢 算出 cd 的中点 c1 以 B1 为起点, c点为控制点, c1为终点画出下面图形:
然后后面按照这样的步骤不断画下去,就可以获得平滑的曲线了。 理论基础我们明白了, 我们改造上面的画线的方法:
实现画出平滑的曲线
上面涉及到求两个点的中间坐标:其实两个坐标的x 和y 分别除以2: 代码如下:
getMid(p1, p2) {
const x = (p1.x + p2.x) / 2
const y = (p1.y + p2.y) / 2
return new Point2d(x, y)
}
我们画出二阶贝塞尔曲线至少所示需要3个点, 所以我们需要数组去存放移动过程中所有的点的信息。
我先实现画贝塞尔曲线的方法:
drawCurve(controlP, endP) {
this.ctx.beginPath()
this.ctx.moveTo(this.startP.x, this.startP.y)
this.ctx.quadraticCurveTo(controlP.x, controlP.y, endP.x, endP.y)
this.ctx.stroke()
this.ctx.closePath()
}
然后在修改move 中的事件
move(e) {
if (!this.isDown) {
return
}
this.endP = this.getPot(e)
this.points.push(this.endP)
if (this.points.length >= 3) {
const [controlP, endP] = this.points.slice(-2)
const middle = this.getMid(controlP, endP)
this.drawCurve(controlP, middle)
this.startP = middle
}
}
这里实现永远取倒数后两个点,然后画完贝塞尔曲线后再将 这个贝塞尔的终点设置为开始点方便下次画。这样是能保证画出连续的贝塞尔曲线的。
我们看下gif 图:
总结
至此本篇文章也算是写完了, 如果你有更好的思路欢迎和我交流,我这只是粗略的表示。canvas画连续平滑的曲线重点——还是怎么去找控制点这一点非常的重要哈!下一篇文章预告: canvas的离屏渲染和webworker的使用。
学习交流
本篇文章所有代码都在我的github上欢迎fork和stark。对可视化感兴趣的可以关注我的公众号【前端图形】,加群 一起学习交流吧!
为了让她学画画——熬夜用canvas实现了一个画板的更多相关文章
- 用canvas画布画一个画板
前段时间,在对H5的回顾中突然对canvas有了感觉,闲来无事便对其进行了一些捯饬.这不,上周我还做了一个好玩的画板呢,废话不多说,直接上代码(PS:翠花,上代码~): HTML部分: <!DO ...
- 一本通 一笔画问题 洛谷P1636 Einstein学画画
P1636 Einstein学画画 相信大家都玩过一笔画这种游戏吧,这其实算得上是我们能够接触到的比较常见的数学问题,有一个很知名的就是七桥问题 这个问题包括所有的一笔画问题都是在欧拉回路的涵盖范围内 ...
- P1636 Einstein学画画
一笔画问题 P1636 Einstein学画画 如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那这个路径叫做欧拉回路. 奇点:跟这个点相邻的边数目有奇数个的点 不存在奇数个奇点的 ...
- YTU 2958: 代码填充--雨昕学画画
2958: 代码填充--雨昕学画画 时间限制: 1 Sec 内存限制: 128 MB 提交: 156 解决: 102 题目描述 雨昕开始学画水彩画,老师给雨昕一个形状(Shape)类,雨昕在Sha ...
- YTU 2953: A代码填充--学画画
2953: A代码填充--学画画 时间限制: 1 Sec 内存限制: 128 MB 提交: 62 解决: 52 题目描述 最近小平迷上了画画,经过琨姐的指导,他学会了RGB色彩的混合方法.对于两种 ...
- 洛谷 P1636 Einstein学画画
P1636 Einstein学画画 题目描述 Einstein学起了画画, 此人比较懒--,他希望用最少的笔画画出一张画... 给定一个无向图,包含n 个顶点(编号1~n),m 条边,求最少用多少笔可 ...
- luoguP1636 Einstein学画画 x
P1636 Einstein学画画 题目描述 Einstein学起了画画, 此人比较懒--,他希望用最少的笔画画出一张画... 给定一个无向图,包含n 个顶点(编号1~n),m 条边,求最少用多少笔可 ...
- Canvas 如何画一个四分之一圆
转: Canvas 如何画一个四分之一圆 HTML: Document JS: var c = document.getElementById('ctx') var ctx = c.getContex ...
- 使用Canvas和JavaScript做一个画板
本文同步于个人博客:https://zhoushuo.me/blog/2018/03/11/drawing-borad/ 前些天学习了HTML5的<canvas>元素,今天就来实践一下,用 ...
随机推荐
- 孟老板 BaseAdapter封装 (二) Healer,footer
BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...
- Spring Cloud04: RestTemplate的使用
上一篇我们已经学会了如何创建一个服务提供者,那么这一篇我们来创建一个服务消费者,实现思路是先通过Spring boot搭建一个微服务应用,再通过Eureka Client把它注册到注册中心Eureka ...
- mybatis在动态 SQL 中使用了参数作为变量,必须要用 @Param 注解
如果在动态 SQL 中使用了参数作为变量,那么就要用 @Param 注解,即使你只有一个参数.如果我们在动态 SQL 中用到了 参数作为判断条件,那么也是一定要加 @Param 注解的,例如如下方法: ...
- 并发王者课-铂金1:探本溯源-为何说Lock接口是Java中锁的基础
欢迎来到<并发王者课>,本文是该系列文章中的第14篇. 在黄金系列中,我们介绍了并发中一些问题,比如死锁.活锁.线程饥饿等问题.在并发编程中,这些问题无疑都是需要解决的.所以,在铂金系列文 ...
- .NET Core/.NET5/.NET6 开源项目汇总1:常用必备组件
系列目录 [已更新最新开发文章,点击查看详细] 开源项目是众多组织与个人分享的组件或项目,作者付出的心血我们是无法体会的,所以首先大家要心存感激.尊重.请严格遵守每个项目的开源协议后再使用.尊 ...
- csp-s模拟测试50(9.22)「施工(单调栈优化DP)」·「蔬菜(二维莫队???)」·「联盟(树上直径)」
改了两天,终于将T1,T3毒瘤题改完了... T1 施工(单调栈优化DP) 考场上只想到了n*hmaxn*hmaxn的DP,用线段树优化一下变成n*hmaxn*log但显然不是正解 正解是很**的单调 ...
- 【odoo14】【开发侧】权限配置
欢迎转载,但需标注出处,谢谢! 说明: 本文面向开发人员,普通用户可参考[odoo14][用户侧]权限配置.文章结构与用户侧一致. 目录 一. odoo中的对象 二. 权限控制 2.1 实现原理 2. ...
- 京东 Vue3 组件库支持小程序开发啦!
源码抢先看: https://github.com/jdf2e/nutui NutUI 3.0 官网:https://nutui.jd.com/3x/#/ 小程序多端适配 设计初衷 在跨端小程序的开发 ...
- 使用 TypeScript,React,ANTLR 和 Monaco Editor 创建一个自定义 Web 编辑器(二)
译文来源 欢迎阅读如何使用 TypeScript, React, ANTLR4, Monaco Editor 创建一个自定义 Web 编辑器系列的第二章节, 在这之前建议您阅读使用 TypeScrip ...
- [HTML]图像标签<img>的用法、属性及路径问题
图像标签:<img> 用法:<img src = "图像地址"> 图像标签的属性 属性 说明 src 指明图像的地址(分为相对路径和绝对路径两 ...