动画.gif

Canvas实现基本画图

JS操作DOM很慢,改用canvas
canvas是用 width 和 height 属性设置宽高的,用CSS设置会拉伸模糊
为了适配手机,需判断是否是触屏

  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>画板</title>
  7. <link rel="stylesheet" href="style.css" />
  8. </head>
  9. <body>
  10. <canvas id="canvas"></canvas>
  11. <script>
  12. const canvas = document.getElementById("canvas");
  13. // 获取文档宽度和高度,设置canvas的html属性
  14. canvas.width = document.documentElement.clientWidth;
  15. canvas.height = document.documentElement.clientHeight;
  16. const ctx = canvas.getContext("2d");
  17. let lineWidth = 6
  18. ctx.lineWidth = lineWidth; // 设置画线宽度
  19. ctx.lineCap = "round"; // 防止转折处断开
  20. let painting = false;
  21. let last = [0, 0];
  22. // 画线函数
  23. function drawLine(lastX, lastY, newX, newY) {
  24. ctx.beginPath();
  25. ctx.moveTo(lastX, lastY);
  26. ctx.lineTo(newX, newY);
  27. ctx.stroke();
  28. }
  29. // 画圆
  30. function drawCircle(x, y, radius) {
  31. ctx.beginPath()
  32. ctx.arc(x, y, radius, 0, 2 * Math.PI, true)
  33. ctx.fill()
  34. }
  35. // 检测是否支持触屏
  36. const isTouchDevice = "ontouchstart" in document.documentElement;
  37. if (isTouchDevice) {
  38. canvas.ontouchstart = (e) => {
  39. const x = e.touches[0].clientX;
  40. const y = e.touches[0].clientY;
  41. last = [x, y];
  42. drawCircle(x, y, lineWidth / 2)
  43. };
  44. canvas.ontouchmove = (e) => {
  45. const x = e.touches[0].clientX;
  46. const y = e.touches[0].clientY;
  47. drawLine(last[0], last[1], x, y);
  48. last = [x, y];
  49. };
  50. } else {
  51. canvas.onmousedown = (e) => {
  52. painting = true;
  53. last = [e.clientX, e.clientY];
  54. drawCircle(e.clientX, e.clientY, lineWidth / 2)
  55. };
  56. canvas.onmousemove = (e) => {
  57. if (painting === true) {
  58. drawLine(last[0], last[1], e.clientX, e.clientY);
  59. last = [e.clientX, e.clientY];
  60. }
  61. };
  62. canvas.onmouseup = () => {
  63. painting = false;
  64. };
  65. }
  66. </script>
  67. </body>
  68. </html>

添加橡皮擦

使用 clearRect API 实现橡皮擦功能

  1. let eraserEnabled = false
  2. canvas.onmousedown = (e) => {
  3. painting = true
  4. last = [e.clientX, e.clientY]
  5. if (eraserEnabled) {
  6. ctx.clearRect(e.clientX - 10, e.clientY - 10, 20, 20)
  7. } else {
  8. drawCircle(e.clientX, e.clientY, lineWidth / 2)
  9. }
  10. }
  11. canvas.onmousemove = (e) => {
  12. if (painting === true) {
  13. if (eraserEnabled) {
  14. ctx.clearRect(e.clientX - 10, e.clientY - 10, 20, 20)
  15. } else {
  16. drawLine(last[0], last[1], e.clientX, e.clientY)
  17. last = [e.clientX, e.clientY]
  18. }
  19. }
  20. }

添加颜色选择功能

image.png

  1. <ul class="colorPicker">
  2. <li class="black active"></li>
  3. <li class="blue"></li>
  4. <li class="red"></li>
  5. <li class="green"></li>
  6. <li class="yellow"></li>
  7. </ul>

利用事件委托,将事件监听放在父元素上

  1. const colors = {
  2. black: '#000',
  3. red: '#ff1a40',
  4. blue: '#1a8cff',
  5. green: '#2bd965',
  6. yellow: '#ffdd33',
  7. }
  8. const colorPicker = document.querySelector('.colorPicker')
  9. colorPicker.addEventListener('click', (e) => {
  10. if (e.target !== e.currentTarget) {
  11. const oldPicked = document.querySelector('.colorPicker > li.active')
  12. if (oldPicked) oldPicked.classList.remove('active')
  13. e.target.classList.add('active')
  14. const classname = e.target.className.replace('active', '').trim()
  15. const pickedColor = colors[classname]
  16. ctx.strokeStyle = pickedColor
  17. ctx.fillStyle = pickedColor
  18. }
  19. })

清空画板

  1. clearBtn.onclick = () => {
  2. ctx.clearRect(0, 0, canvas.width, canvas.height)
  3. }

保存成图片

canvas默认背景是透明的,先写个初始化背景函数, 在程序开始以及清空画布时调用

  1. let pickedColor = '#000'
  2. const initBG = () => {
  3. ctx.fillStyle = '#fff'
  4. ctx.fillRect(0, 0, canvas.width, canvas.height)
  5. ctx.fillStyle = pickedColor
  6. }

保存成图片的方法

  1. save.onclick = () => {
  2. const url = canvas.toDataURL('image/jpeg')
  3. const a = document.createElement('a')
  4. document.body.append(a)
  5. a.href = url
  6. a.download = 'my-drawing'
  7. a.target = '_blank'
  8. a.click()
  9. }

修改画笔粗细

image.png
通过 ctx.lineWidth 属性修改画笔粗细
通过 e.target.nodeName 判断点击的是什么元素
该功能更多的是CSS样式的编写,但也没什么难度,就不贴上代码了

  1. const thicknessHash = {
  2. 1: 4,
  3. 2: 7,
  4. 3: 10,
  5. 4: 13,
  6. 5: 16,
  7. }
  8. const thicknessCircle = document.querySelector(
  9. '.thickness > .circle-wrapper > .circle'
  10. )
  11. const thicknessNumber = document.querySelector('.thickness > .number')
  12. const thicknessPicker = document.querySelector('.thickness-picker > ul')
  13. thicknessPicker.onclick = (e) => {
  14. if (e.target.nodeName === 'LI') {
  15. const oldPicked = document.querySelector(
  16. '.thickness-picker > ul > li.active'
  17. )
  18. oldPicked.classList.remove('active')
  19. e.target.classList.add('active')
  20. const num = e.target.innerText
  21. ctx.lineWidth = thicknessHash[num]
  22. thicknessCircle.style.width = thicknessHash[num] + 'px'
  23. thicknessCircle.style.height = thicknessHash[num] + 'px'
  24. thicknessNumber.innerText = num
  25. }
  26. }

总结

整个 Canvas画板 都用原生JS编写,实现了画笔颜色选取,画笔粗细切换,橡皮擦,一键清空画布以及保存图片等功能,项目中用的关键技术有:Canvas API, DOM原生API,事件委托,flex布局,iconfont svg图标,项目使用了 parcel 编译和打包。
预览链接
源代码