本文同步于个人博客:https://zhoushuo.me/blog/2018/03/11/drawing-borad/

前些天学习了HTML5<canvas>元素,今天就来实践一下,用canvas做一个画板。

首先说一下要实现的功能:

  • 切换画笔颜色
  • 调整笔刷粗细
  • 清空画布
  • 橡皮擦擦除
  • 撤销操作
  • 保存成图片
  • 兼容移动端(支持触摸)

好了,废话少说,先看最终效果:https://zhoushuozh.github.io/drawingborad

准备工作

首先,准备个容器,也就是画板了。

<canvas id="drawing-board"></canvas>

然后初始化js:

let canvas = document.getElementById("drawing-board");
let ctx = canvas.getContext("2d");

我想把画板做成全屏的,所以接下来设置一下canvas的宽高。

let pageWidth = document.documentElement.clientWidth;
let pageHeight = document.documentElement.clientHeight; canvas.width = pageWidth;
canvas.height = pageHeight;

由于部分IE不支持canvas,如果要兼容IE,我们可以创建一个canvas,然后使用excanvas初始化,针对IE加上exCanvas.js,这里我们暂时先不考虑IE。

实现一个简单的画板

实现思路:监听鼠标事件,用drawCircle()方法把记录的数据画出来。

  1. 设置初始化当前画布功能为画笔状态,painting = false
  2. 当鼠标按下时(mousedown),把painting设为true,表示正在画,鼠标没松开。把鼠标点记录下来。
  3. 当按下鼠标的时候,鼠标移动(mousemove)就把点记录下来并画出来。
  4. 如果鼠标移动过快,浏览器跟不上绘画速度,点与点之间会产品间隙,所以我们需要将画出的点用线连起来(lineTo())。
  5. 鼠标松开的时候(mouseup),把painting设为false

代码:

let painting = false;
let lastPoint = {x: undefined, y: undefined}; //鼠标按下事件
canvas.onmousedown = function (e) {
painting = true;
let x = e.clientX;
let y = e.clientY;
lastPoint = {"x": x, "y": y};
drawCircle(x, y, 5);
}; //鼠标移动事件
canvas.onmousemove = function (e) {
if (painting) {
let x = e.clientX;
let y = e.clientY;
let newPoint = {"x": x, "y": y};
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y,clear);
lastPoint = newPoint;
}
}; //鼠标松开事件
canvas.onmouseup = function () {
painting = false;
} // 画点函数
function drawCircle(x, y, radius) {
ctx.save();
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
} // 划线函数
function drawLine(x1, y1, x2, y2) {
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.closePath();
}

橡皮擦功能

实现思路

  1. 获取橡皮擦元素
  2. 设置橡皮擦初始状态,clear = false
  3. 监听橡皮擦click事件,点击橡皮擦,改变橡皮擦状态,clear = true
  4. cleartrue时,移动鼠标使用canvas剪裁(clip())擦除画布。
let eraser = document.getElementById("eraser");
let clear = false; eraser.onclick = function () {
clear = true;
}; ...
if (clear) {
ctx.save();
ctx.globalCompositeOperation = "destination-out";
ctx.stroke();
ctx.closePath();
ctx.clip();
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore();
}
...

注意,在canvas中的裁剪和平时的裁剪功能不一样在这里,裁剪是指在裁剪区域去显示我们所画的图

兼容移动端

实现思路:

  1. 判断设备是否支持触摸
  2. true,则使用touch事件;false,则使用mouse事件

代码:

...
if (document.body.ontouchstart !== undefined) {
// 使用touch事件
anvas.ontouchstart = function (e) {
// 开始触摸
}
canvas.ontouchmove = function (e) {
// 开始滑动
}
canvas.ontouchend = function () {
// 滑动结束
}
}else{
// 使用mouse事件
...
}
...

这里需要注意的一点是,在touch事件里,是通过.touches[0].clientX.touches[0].clientY来获取坐标的,这点要和mouse事件区别开。

切换画笔颜色

实现思路:

  1. 获取颜色元素节点。
  2. 点击颜色返回其颜色值,并赋给画布的ctx.strokeStyle

代码:

let aColorBtn = document.getElementsByClassName("color-item");

for (let i = 0; i < aColorBtn.length; i++) {
aColorBtn[i].onclick = function () {
for (let i = 0; i < aColorBtn.length; i++) {
activeColor = this.style.backgroundColor;
ctx.strokeStyle = activeColor;
}
}

清空画布

实现思路:

  1. 获取元素节点。
  2. 点击清空按钮清空canvas画布。

代码:

let reSetCanvas = document.getElementById("clear");

reSetCanvas.onclick = function () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
};

调整笔刷粗细

实现思路:

  1. 创建input[type=range]
  2. 滑动range获取其value值,并赋给ctx.lineWidth

代码:

let range = document.getElementById("range");

range.onchange = function(){
lWidth = this.value;
};

保存成图片

实现思路:

  1. 获取canvas.toDateURL
  2. 在页面里创建并插入一个a标签
  3. a标签href等于canvas.toDateURL,并添加download属性
  4. 点击保存按钮,a标签触发click事件

代码:

let save = document.getElementById("save");

save.onclick = function () {
let imgUrl = canvas.toDataURL("image/png");
let saveA = document.createElement("a");
document.body.appendChild(saveA);
saveA.href = imgUrl;
saveA.download = "zspic" + (new Date).getTime();
saveA.target = "_blank";
saveA.click();
};

撤销

实现思路:

  1. 准备一个数组记录历史操作
  2. 每次使用画笔前将当前绘图表面储存进数组
  3. 点击撤销时,恢复到上一步的绘图表面

代码:

canvas.ontouchstart = function (e) {
// 在这里储存绘图表面
this.firstDot = ctx.getImageData(0, 0, canvas.width, canvas.height);
saveData(this.firstDot);
...
} let undo = document.getElementById("undo");
let historyDeta = []; function saveData (data) {
(historyDeta.length === 10) && (historyDeta.shift()); // 上限为储存10步,太多了怕挂掉
historyDeta.push(data);
}
undo.onclick = function(){
if(historyDeta.length < 1) return false;
ctx.putImageData(historyDeta[historyDeta.length - 1], 0, 0);
historyDeta.pop()
};

因为每次储存都是将一张图片存入内存,对性能影响较大,所以在这里设置了储存上限。

总结

这里用的知识点主要有:监听mousetouch事件,以及canvasmoveTo()lineTo()stroke()clip()clearRect()等方法。其实还有很多效果可以实现,比如说喷雾效果,蜡笔效果,艺术画效果等等。日后有时间我会继续对这个画板进行优化,增加一些新的功能,同时欢迎大家留言提问或者提出批评建议。

最终代码:https://github.com/zhoushuozh/drawingborad

使用Canvas和JavaScript做一个画板的更多相关文章

  1. 用html5的canvas和JavaScript创建一个绘图程序

    本文将引导你使用canvas和JavaScript创建一个简单的绘图程序. 创建canvas元素 首先准备容器Canvas元素,接下来所有的事情都会在JavaScript里面. <canvas ...

  2. 用javascript做一个视频播放器

    以前我们在网页上播放视频,都是要麻烦flash来实现.看着那一大段的<object>真心觉得累.随着html5的不断普及,现在是时候使用html5提供的video元素来做点正经事了,但是要 ...

  3. html,CSS,javascript 做一个弹窗

    弹窗的工作原理:在网页中写一个div ,布局到想要显示的位置,将display设为none,隐藏该div.然后通过点击事件或其他操作,利用Js代码,将display设置为block,将div 显示到网 ...

  4. 如何用JavaScript做一个可拖动的div层

    可拖动的层在Web设计中用处很多,比如在某些需要自定义风格布局的应用中,控件就需要拖动操作,下面介绍一个,希望可以满足你的需求,顺便学习一下可拖动的层是如何实现的. 下面是效果演示: 这个DIV可以移 ...

  5. 用Javascript做一个“获取验证码”的按钮

    要求:①点击按钮后背景色会发生改变②有倒计时(一般为30秒) <!DOCTYPE HTML> <html> <head> <meta charset=&quo ...

  6. 用JavaScript做一个小小设计

    这个项目是我无聊时完成的,参阅过很多大神的示例,其实方法并不难主要是js和css样式的设计,我发现自己还有很多的js代码写不出来更加不用提看的明白了,(PS吐槽一下:革命尚未成功,同志还需努力啊!)此 ...

  7. 【Bugly干货分享】一起用 HTML5 Canvas 做一个简单又骚气的粒子引擎

    Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言 好吧,说是“粒子引擎”还是大言不 ...

  8. 一起用HTML5 canvas做一个简单又骚气的粒子引擎

    前言 好吧,说是"粒子引擎"还是大言不惭而标题党了,离真正的粒子引擎还有点远.废话少说,先看demo 本文将教会你做一个简单的canvas粒子制造器(下称引擎). 世界观 这个简单 ...

  9. (Demo分享)利用JavaScript(JS)做一个可输入分钟的倒计时钟功能

    利用JavaScript(JS)实现一个可输入分钟的倒计时钟功能本文章为 Tz张无忌 原创文章,转载请注明来源,谢谢合作! 网络各种利用JavaScript做倒计时的Demo对新手很不友好,这里我亲手 ...

随机推荐

  1. lsof、tcpdump和wireshark

    lsof:https://www.jianshu.com/p/a3aa6b01b2e1 tcpdump:https://linux.cn/article-10191-1.html wireshark: ...

  2. MySQL — DCL语言

    全称 Data Control Language.数据控制语言,用来创建数据库用户.控制数据库的访问权限. 1.用户管理 1.1.查询用户 select * from user; 1.2.创建用户 - ...

  3. 递归——深度优先搜索(DFS)——以滑雪问题为例(自顶而下)

    一.问题:滑雪 问题描述:小明喜欢滑雪,为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.小明想知道在一个区域中最长底滑坡.区域由一个二维数组给出.数组的每 ...

  4. 微信小程序结合原生JS实现电商模板(二)

    接 <微信小程序结合原生JS实现电商模板(一)>,在首页列表加入购物车到购物和模块增删数量,动态计算商品价格实现后,本次提交主要实现了商品详情(还不完善)简单页面,从商品详情页跳转到购物车 ...

  5. 报错 ———— Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 6; 不允许有匹配 "[xX][mM][lL]" 的处理指令目标。

    报错 <?xml version="1.0" encoding="UTF-8"?>  必须是XML文件的第一个元素且前面不能空格. ### Erro ...

  6. php——字符串的""和null,empty的关系

    public function test(){ $test = ""; if($test==null){ echo "test==null <hr>" ...

  7. Centos7下开启防火墙,允许通过的端口

    1.查看防火墙状态 systemctl status firewalld 2.如果不是显示active状态,需要打开防火墙 systemctl start firewalld 3.# 查看所有已开放的 ...

  8. fastcgi未授权访问及任意命令执行

    1. 漏洞原理 服务端使用fastcgi协议并对外网开放9000端口,攻击者可以构造fastcgi协议包内容,实现未授权访问服务端.php文件以及执行任意命令. 2. 漏洞利用 第一步 搭建vulhu ...

  9. xx局点FusionCloud6.3 type1 计算配额失败问题

    现象: 排查过程: 1.登录云平台部署面,选择部署资源-服务器 2.找到MOC-ManageOne-Service01.MOC-ManageOne-Service02两台机器ip地址. 3.用ssh工 ...

  10. web测试知识点整理

    web是如何测试的? 1. 通用功能测试和可用性测试 2. 性能测试和安全性测试 3. 兼容性测试 4. 数据库和稳定性测试等 web功能测试怎么测? 从一下几个方面来进行WEB测试: 1. 链接测试 ...