前言

  PC端测试:QQ浏览器全屏绘画完成、缩小时内容会被清空,切换背景颜色内容会被重置,其他暂无发现;

手机端测试:微信内置浏览器不通过;Safari 浏览器使用画笔时没固定页面会有抖动效果,使用橡皮擦功能 能绘制出点线(黑人问号脸出现),保存成图片时需要手动保存(能理解),撤销操作?(em 黑人问号再次出现);

手机机型系统:iphone 7p , ios 12

  写的有意思,就搬来了重要内容供参考

  原文地址: https://juejin.im/post/5c7bf106e51d454b47558882

  补充了一些自己的想法在其中o(* ̄▽ ̄*)ブ~~~

  如果有什么错误地方请指出,感谢思密达~~

一、介绍

  名称: 智绘画板

  技术栈: HTML5,CSS3,JavaScript,移动端

  功能描述:

    • 支持PC端和移动端在线绘画功能

    • 实现任意选择画笔颜色、调整画笔粗细以及橡皮檫擦除等绘画功能

    • 实现在线画板的本地保存功能

    • 支持撤销和返回操作

    • 自定义背景颜色

二、项目效果展示

   项目地址

   预览地址

  注:下面实现项目效果主要是关于JavaScript方面的,下面仅仅是提供实现思路的代码,并非全部代码。

三、一步步实现项目效果

  (一)分析页面

    通过用例图,我们知道用户进入我们这个网站有哪些功能?

    用户可以进行的操作:

      • 画画

      • 改变画笔的粗细

      • 切换画笔的颜色

      • 使用橡皮檫擦除不想要的部分

      • 清空画板

      • 将自己画的东西保存成图片

      • 进行撤销和重做操作

      • 切换画板背景颜色

      • 兼容移动端(支持触摸)

  (二)进行HTML布局

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>智绘画板</title>
<link rel="shortcut icon" href="./image/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="./css/style.css">
</head>
<body>
<canvas id="canvas"></canvas>
<div class="bg-btn"></div>
<div class="color-group" id="bgGroup">
<h3>选择背景颜色:</h3>
<ul class="clearfix">
<li class="bgcolor-item" style="background-color: blue;"></li>
<li class="bgcolor-item" style="background-color: black;"></li>
<li class="bgcolor-item" style="background-color: #FF3333;"></li>
<li class="bgcolor-item" style="background-color: #0066FF;"></li>
<li class="bgcolor-item" style="background-color: #FFFF33;"></li>
<li class="bgcolor-item" style="background-color: #33CC66;"></li>
<li class="bgcolor-item" style="background-color: gray;"></li>
<li class="bgcolor-item" style="background-color: #F34334;"></li>
<li class="bgcolor-item" style="background-color: #fff;box-shadow: 0 1px 2px 0 rgba(32,33,36,0.28);"></li>
<li class="bgcolor-item" style="background-color: #9B27AC;"></li>
<li class="bgcolor-item" style="background-color: #4CB050;"></li>
<li class="bgcolor-item" style="background-color: #029688;"></li>
</ul>
<i class="closeBtn"></i>
</div>
<div class="tools">
<div class="container">
<button class="save" id="save" title="保存"></button>
<button class="brush active" id="brush" title="画笔"></button>
<button class="eraser" id="eraser" title="橡皮擦"></button>
<button class="clear" id="clear" title="清屏"></button>
<button class="undo" id="undo" title="撤销"></button>
<button class="redo" id="redo" title="再做"></button>
</div>
</div>
<div class="pen-detail" id="penDetail">
<i class="closeBtn"></i>
<p>笔大小</p>
<span class="circle-box"><i id="thickness"></i></span> <input type="range" id="range1" min="1" max="10" value="1">
<p>笔颜色</p>
<ul class="pen-color clearfix">
<li class="color-item active" style="background-color: black;"></li>
<li class="color-item" style="background-color: #FF3333;"></li>
<li class="color-item" style="background-color: #99CC00;"></li>
<li class="color-item" style="background-color: #0066FF;"></li>
<li class="color-item" style="background-color: #FFFF33;"></li>
<li class="color-item" style="background-color: #33CC66;"></li>
</ul>
<p>不透明度</p>
<i class="showOpacity"></i> <input type="range" id="range2" min="1" max="10" value="1">
</div>
<script src="./js/main.js"></script>
</body>
</html>

  (三)用CSS美化界面

    css代码优化界面

  (四)使用JS实现项目的具体功能

    1.准备工作

      首先,准备个容器。

    <canvas id="canvas"></canvas>

      然后初始化js

let canvas = document.getElementById('canvas');
let context = 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。

      但是我在电脑上对浏览器的窗口进行改变,画板不会自适应的放缩。解决办法:

// 记得要执行autoSetSize这个函数哦
function autoSetSize(){
canvasSetSize();
// 当执行这个函数的时候,会先设置canvas的宽高
function canvasSetSize(){
let pageWidth = document.documentElement.clientWidth;
let pageHeight = document.documentElement.clientHeight; canvas.width = pageWidth;
canvas.height = pageHeight;
}
// 在窗口大小改变之后,就会触发resize事件,重新设置canvas的宽高
window.onresize = function(){
canvasSetSize();
}
}

    2.实现画画的功能

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

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

    注:drawCircle这个方法其实可以不用书写,这个只是为了让大家能够理解开始点击的位置在哪里?

function listenToUser() {
// 定义一个变量初始化画笔状态
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);
lastPoint = newPoint;
}
} // 鼠标松开事件
canvas.onmouseup = function(){
painting = false;
}
} // 画点函数
function drawCircle(x,y,radius){
// 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
context.beginPath();
// 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),
// 从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。
context.arc(x,y,radius,0,Math.PI*2);
// 通过填充路径的内容区域生成实心的图形
context.fill();
// 闭合路径之后图形绘制命令又重新指向到上下文中。
context.closePath();
} function drawLine(x1,y1,x2,y2){
// 设置线条宽度
context.lineWidth = 10;
// 设置线条末端样式。
context.lineCap = "round";
// 设定线条与线条间接合处的样式
context.lineJoin = "round";
// moveTo(x,y)将笔触移动到指定的坐标x以及y上
context.moveTo(x1,y1);
// lineTo(x, y) 绘制一条从当前位置到指定x以及y位置的直线
context.lineTo(x2,y2);
// 通过线条来绘制图形轮廓
context.stroke();
context.closePath();
}

    3.实现橡皮擦功能

      实现思路:

      • 获取橡皮擦元素
      • 设置橡皮擦初始状态,eraserEnabled = false
      • 监听橡皮擦click事件,点击橡皮擦,改变橡皮擦状态,eraserEnabled = true,并且切换class,实现被激活的效果。
      • eraserEnabledtrue,移动鼠标用context.clearRect()实现了橡皮檫。

      但是我发现canvas的API中,可以清除像素的就是clearRect方法

      但是clearRect方法的清除区域矩形,毕竟大部分人的习惯中的橡皮擦都是圆形的,所以就引入了剪辑区域这个强大的功能,也就是clip方法。

      下面的代码是使用context.clearRect()实现了 橡皮檫。请看踩坑部分,了解如何更好的实现橡皮檫。

let eraser = document.getElementById("eraser");
let eraserEnabled = false; // 记得要执行listenToUser这个函数哦
function listenToUser() {
// ... 代表省略了之前写的代码
// ... // 鼠标按下事件
canvas.onmousedown = function(e){
// ...
if(eraserEnabled){//要使用eraser
context.clearRect(x-5,y-5,10,10)
}else{
lastPoint = {'x':x,'y':y}
}
} // 鼠标移动事件
canvas.onmousemove = function(e){
let x = e.clientX;
let y = e.clientY;
if(!painting){return}
if(eraserEnabled){
context.clearRect(x-5,y-5,10,10);
}else{
var newPoint = {'x':x,'y':y};
drawLine(lastPoint.x, lastPoint.y,newPoint.x, newPoint.y);
lastPoint = newPoint;
}
} // ...
} // 点击橡皮檫
eraser.onclick = function(){
eraserEnabled = true;
eraser.classList.add('active');
brush.classList.remove('active');
}

    4.实现清屏功能

实现思路:

      • 获取元素节点。

      • 点击清空按钮清空canvas画布。

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

// 实现清屏
reSetCanvas.onclick = function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
setCanvasBg('white');
} // 重新设置canvas背景颜色
function setCanvasBg(color) {
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}

    5.实现保存成图片功能

实现思路:

      • 获取canvas.toDateURL

      • 在页面里创建并插入一个a标签

      • a标签href等于canvas.toDateURL,并添加download属性

      • 点击保存按钮,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 = 'mypic'+(new Date).getTime();
saveA.target = '_blank';
saveA.click();
}

    6.实现改变背景颜色的功能

实现思路:

      • 获取相应的元素节点。

      • 给每一个class为bgcolor-item的标签添加点击事件,当点击事件触发时,改变背景颜色。

      • 点击设置背景颜色的div之外的地方,实现隐藏那个div。

let selectBg = document.querySelector('.bg-btn');
let bgGroup = document.querySelector('.color-group');
let bgcolorBtn = document.querySelectorAll('.bgcolor-item');
let penDetail = document.getElementById("penDetail");
let activeBgColor = '#fff'; // 实现了切换背景颜色
for (let i = 0; i < bgcolorBtn.length; i++) {
bgcolorBtn[i].onclick = function (e) {
// 阻止冒泡
e.stopPropagation();
for (let i = 0; i < bgcolorBtn.length; i++) {
bgcolorBtn[i].classList.remove("active");
this.classList.add("active");
activeBgColor = this.style.backgroundColor;
setCanvasBg(activeBgColor);
}
}
} document.onclick = function(){
bgGroup.classList.remove('active');
} selectBg.onclick = function(e){
bgGroup.classList.add('active');
e.stopPropagation();
}

    7.实现改变画笔粗细的功能

实现思路:

      • 实现让设置画笔的属性的对话框出现。

      • 获取相应的元素节点。

      • 当input=range的元素发生改变的时候,获取到的值赋值给lWidth。

      • 然后设置context.lineWidth = lWidth

let range1 = document.getElementById('range1');
let lWidth = 2;
let ifPop = false; range1.onchange = function(){
console.log(range1.value);
console.log(typeof range1.value)
thickness.style.transform = 'scale('+ (parseInt(range1.value)) +')';
console.log(thickness.style.transform )
lWidth = parseInt(range1.value*2);
} // 画线函数
function drawLine(x1,y1,x2,y2){
// ...
context.lineWidth = lWidth;
// ...
} // 点击画笔
brush.onclick = function(){
eraserEnabled = false;
brush.classList.add('active');
eraser.classList.remove('active');
if(!ifPop){
// 弹出框
console.log('弹一弹')
penDetail.classList.add('active');
}else{
penDetail.classList.remove('active');
}
ifPop = !ifPop;
}

    8.实现改变画笔颜色的功能

实现思路跟改变画板背景颜色的思路类似。

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

getColor();

function getColor(){
for (let i = 0; i < aColorBtn.length; i++) {
aColorBtn[i].onclick = function () {
for (let i = 0; i < aColorBtn.length; i++) {
aColorBtn[i].classList.remove("active");
this.classList.add("active");
activeColor = this.style.backgroundColor;
ctx.fillStyle = activeColor;
ctx.strokeStyle = activeColor;
}
}
}
}

    9.实现改变撤销和重做的功能

实现思路:

      • 保存快照:每完成一次绘制操作则保存一份 canvas 快照到 canvasHistory 数组(生成快照使用 canvas 的 toDataURL() 方法,生成的是 base64 的图片);

      • 撤销和反撤销:把 canvasHistory 数组中对应索引的快照使用 canvas 的 drawImage() 方法重绘一遍;

      • 绘制新图像:执行新的绘制操作时,删除当前位置之后的数组记录,然后添加新的快照。

let undo = document.getElementById("undo");
let redo = document.getElementById("redo"); // ...
canvas.onmouseup = function(){
painting = false;
canvasDraw();
} let canvasHistory = [];
let step = -1; // 绘制方法
function canvasDraw(){
step++;
if(step < canvasHistory.length){
canvasHistory.length = step; // 截断数组
}
// 添加新的绘制到历史记录
canvasHistory.push(canvas.toDataURL());
} // 撤销方法
function canvasUndo(){
if(step > 0){
step--;
// ctx.clearRect(0,0,canvas.width,canvas.height);
let canvasPic = new Image();
canvasPic.src = canvasHistory[step];
canvasPic.onload = function () { ctx.drawImage(canvasPic, 0, 0); }
undo.classList.add('active');
}else{
undo.classList.remove('active');
alert('不能再继续撤销了');
}
}
// 重做方法
function canvasRedo(){
if(step < canvasHistory.length - 1){
step++;
let canvasPic = new Image();
canvasPic.src = canvasHistory[step];
canvasPic.onload = function () {
// ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(canvasPic, 0, 0);
}
redo.classList.add('active');
}else {
redo.classList.remove('active')
alert('已经是最新的记录了');
}
}
undo.onclick = function(){
canvasUndo();
}
redo.onclick = function(){
canvasRedo();
}

    10.兼容移动端

  实现思路:

      • 判断设备是否支持触摸

      • true,则使用touch事件;false,则使用mouse事件

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

四、踩坑

  问题1:在电脑上对浏览器的窗口进行改变,画板不会自适应

  解决办法:

onresize响应事件处理中,获取到的页面尺寸参数是变更后的参数 。

当窗口大小发生改变之后,重新设置canvas的宽高,简单来说,就是窗口改变之后,给canvas.width和canvas.height重新赋值。

// 记得要执行autoSetSize这个函数哦
function autoSetSize(){
canvasSetSize();
// 当执行这个函数的时候,会先设置canvas的宽高
function canvasSetSize(){
let pageWidth = document.documentElement.clientWidth;
let pageHeight = document.documentElement.clientHeight; canvas.width = pageWidth;
canvas.height = pageHeight;
}
// 在窗口大小改变之后,就会触发resize事件,重新设置canvas的宽高
window.onresize = function(){
canvasSetSize();
}
}

  问题2:当绘制线条宽度比较小的时候还好,一旦比较粗就会出现问题

  解决办法:

    看一下文档,得出方法,只需要简单修改一下绘制线条的代码就行

 // 画线函数
function drawLine(x1,y1,x2,y2){
context.beginPath();
context.lineWidth = lWidth;
//-----加入-----
// 设置线条末端样式。
context.lineCap = "round";
// 设定线条与线条间接合处的样式
context.lineJoin = "round";
//-----加入-----
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.stroke();
context.closePath();
}

  问题3:如何实现圆形的橡皮檫?

  解决办法:

    canvas的API中,可以清除像素的就是clearRect方法,但是clearRect方法的清除区域矩形,毕竟大部分人的习惯中的橡皮擦都是圆形的,所以就引入了剪辑区域这个强大的功能,也就是clip方法。用法很简单: 

ctx.save()
ctx.beginPath()
ctx.arc(x2,y2,a,0,2*Math.PI);
ctx.clip()
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.restore();

    上面那段代码就实现了圆形区域的擦除,也就是先实现一个圆形路径,然后把这个路径作为剪辑区域,再清除像素就行了。

    有个注意点就是需要先保存绘图环境,清除完像素后要重置绘图环境,如果不重置的话以后的绘图都是会被限制在那个剪辑区域中。

  问题4:如何兼容移动端?

  1.添加meta标签

    因为浏览器初始会将页面现在手机端显示时进行缩放,因此我们可以在meta标签中设置meta viewport属性,告诉浏览器不将页面进行缩放,页面宽度=用户设备屏幕宽度

<meta name="viewport" content="width=device-width,
initial-scale=1,user-scalable=no,
maximum-scale=1.0,minimum-scale=1.0"/> /*
页面宽度=移动宽度 :width=device-width
用户不可以缩放:user-scalable=no
缩放比例:initial-scale=1
最大缩放比例:maximum-scale=1.0
最小缩放比例:minimum-scale=1.0
*/

  2.在移动端几乎使用的都是touch事件,与PC端不同

    由于移动端是触摸事件,所以要用到H5的属性touchstart/touchmove/touchend,但是PC端只支持鼠标事件,所以要进行特性检测。

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

  问题5:出现一个问题就是清空之后,重新画,然后出现原来的画的东西

    这个嘛,问题不大,只不过是我漏写context.beginPath();

    也花了一点时间在上面解决bug,让我想起“代码千万行,注释第一行;编程不规范,同事两行泪 ”,还是按照文档操作规范操作好,

    真香!!!

【分享】用Canvas实现画板功能的更多相关文章

  1. canvas实现画板功能(渐变、动画、阴影...)

    刚刚在博客园落户了,希望能在这认识更多大神,希望能和大家交好朋友. 闲来无事,把以前用canvas写的画板代码改进了一番,用Html5提供的表单标签给其 加了一个选择颜色的功能,因此发现了该标签的一个 ...

  2. Canvas现实画板功能

    先看图片 HTML <!doctype html> <html lang="en"> <head> <meta charset=" ...

  3. 一款基于HTML5 Canvas的画板涂鸦动画

    今天给各网友分享一款基于HTML5 Canvas的画板涂鸦动画.记得之前我们分享过一款HTML5 Canvas画板工具,可以切换不同的笔刷,功能十分强大.本文今天要再来分享一款基于HTML5 Canv ...

  4. 基于canvas的画板

    最近重新在看Html5&CSS3的知识,看到canvas的时候,想到了以前在学校学计算机图形学时做过的画图实验,于是想,可以基于html5和css3来做一款画板,经过1天的努力,完成了画板的一 ...

  5. canvas实现画板

    canvas实现画板 主要用到知识点: 鼠标事件onmousedown() onmousemove() onmouseup() onmouseleave() 事件委托 canvas的一些方法 如:绘制 ...

  6. canvas小画板--(1)平滑曲线

    功能需求 项目需求:需要实现一个可以自由书写的小画板 简单实现 对于熟悉canvas的同学来说,这个需求很简单,短短几十行代码就能实现: <!doctype html> <html& ...

  7. canvas小画板——(2)荧光笔效果

    我们在上一篇文章中讲了如何绘制平滑曲线 canvas小画板——(1)平滑曲线. 透明度实现荧光笔 现在我们需要加另外一种画笔效果,带透明度的荧光笔.那可能会觉得绘制画笔的时候加上透明度就可以了.我们来 ...

  8. html5 canvas 实现倒计时 功能

    function showTime(a) { var b = { id: "showtime", //canvasid x: 60, //中心点坐标 X轴; y: 60, //中心 ...

  9. iOS实现白板、画板功能,有趣的涂鸦工具,已封装,简单快捷使用

    一.效果图: 二.选择颜色: 分[固定颜色模式]和[自由取模式].  三.操作栏功能: 1.撤销:撤销上一步操作,可一直往上进行,直到全部清空. 2.清空:直接清除所有绘画. 3.橡皮擦:去除不要的绘 ...

随机推荐

  1. Ubuntu 安装第三方工具

    1. pycharm  安装(链接:https://pan.baidu.com/s/1fIp-AhBmnPvqYW40140RLw     提取码:ukkv ) 1.运行以下命令安装 sh pycha ...

  2. ok6410如何从sdram中启动uboot 调试 这是一个猜想还没有验证

    1\在smdk6410.h中定义宏 //#define CONFIG_SKIP_LOWLEVEL_INIT 1 //#define CONFIG_SKIP_RELOCATE_UBOOT 12\将编译的 ...

  3. python基础部分----文件、copy、内存指针

    0.来源:https://www.cnblogs.com/jin-xin/articles/9439483.html 1.is VS id() VS == 2.小数据池.代码块缓存机制 3.赋值符号= ...

  4. Yii1操作phpexcel

    Yii::import('application.vendors.phpexcel.*'); Yii::import('application.vendors.phpexcel.PHPExcel.*' ...

  5. Magento 2.2.5和2.2.6的bug 产品设置special price又删除后价格排序有误

    Magento 2.2.5和2.2.6的bug 产品设置special price又删除后价格排序有误 一.问题描述:版本2.2.5和2.2.6均有此问题,为Magento2的系统bug.为产品设置s ...

  6. [系统相关]WPS Office 2016 专业增强版 10.8.0.6470 免序列号无限制

    WPS Office (10.8.0.6470)  新增功能列表 ============================================= 改进功能列表 ------------ W ...

  7. Bigger-Mai 养成计划,Python基础巩固四

    一.装饰器:定义:本质是函数,(装饰其他函数)就是为其他函数添加附加功能.原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰函数的调用方式实现装饰器的知识储备:1.函数即‘变量’2.高阶函数 ...

  8. [十二省联考2019]异或粽子 01trie

    [十二省联考2019]异或粽子 01trie 链接 luogu 思路 首先求前k大的(xo[i]^xo[j])(i<j). 考场上只想到01trie,不怎么会写可持久,就写了n个01trie,和 ...

  9. linux 添加环境变量

    You have to edit three files to set a permanent environment variable as follow: ~/.bashrc When you o ...

  10. hadoop配置项笔记 - streaming

    stream.map.input.ignoreKey 作用:是否忽略key,只传递值. 默认值:false 我的设置:按需要更改 stream.map.input.field.separator st ...