1. <canvas id="canvasId" width="1000" height="800"></canvas>

使用pointer事件监听,落笔,拖拽,收笔。

  1. document.onpointerdown = function (e) {
  2. if (e.type == "touchstart")
  3. handwriting.down(e.touches[0].pageX, e.touches[0].pageY);
  4. else
  5. handwriting.down(e.x, e.y);
  6. }
  7. document.onpointermove = function (e) {
  8. if (e.type == "touchmove")
  9. handwriting.move(e.touches[0].pageX, e.touches[0].pageY);
  10. else
  11. handwriting.move(e.x, e.y);
  12. }
  13. document.onpointerup = function (e) {
  14. if (e.type == "touchend")
  15. handwriting.up(e.touches[0].pageX, e.touches[0].pageY);
  16. else
  17. handwriting.up(e.x, e.y);
  18. }

主要的逻辑在Handwritinglff 上,存储了当前绘制中的线条的所有点集合,所有绘制过的线条集合pointLines 。

  1. class Handwritinglff {
  2. constructor(canvas) {
  3. this.canvas = canvas;
  4. this.ctx = canvas.getContext("2d")
  5. this.line = new Line();
  6. this.pointLines = new Array();//Line数组
  7. this.k = 0.5;
  8. this.begin = null;
  9. this.middle = null;
  10. this.end = null;
  11. this.lineWidth = 10;
  12. this.isDown = false;
  13. }

down事件的时候初始化当前绘制线条line;

move事件的时候将点加入到当前线条line,并开始绘制

up的时候将点加入绘制线条,并绘制完整一条线。

需要注意的点:

加入点的时候,距离太近的点不需要重复添加;

很简单!就是在一条线段的最后几个点的lineWidth不断减小,我们这里选用了最后6个点,如果只选用六个阶梯变化,效果是很难看的,会看到一节节明显的线条变细的过程,如下图:

 

所以我们有个关键的补点过程,我们会再每6个像素之间补一个点,根据线条粗细变化的范围和最后计算出来的点数,就可以知道每两点连线lineWidth的粗细。

这里的补点过程我们用到了在贝塞尔曲线上补点的算法。具体不明白的可以留言哈

  1. bezierCalculate(poss, precision) {
  2. //维度,坐标轴数(二维坐标,三维坐标...)
  3. let dimersion = 2;
  4. //贝塞尔曲线控制点数(阶数)
  5. let number = poss.length;
  6. //控制点数不小于 2 ,至少为二维坐标系
  7. if (number < 2 || dimersion < 2)
  8. return null;
  9. let result = new Array();
  10. //计算杨辉三角
  11. let mi = new Array();
  12. mi[0] = mi[1] = 1;
  13. for (let i = 3; i <= number; i++) {
  14. let t = new Array();
  15. for (let j = 0; j < i - 1; j++) {
  16. t[j] = mi[j];
  17. }
  18. mi[0] = mi[i - 1] = 1;
  19. for (let j = 0; j < i - 2; j++) {
  20. mi[j + 1] = t[j] + t[j + 1];
  21. }
  22. }
  23. //计算坐标点
  24. for (let i = 0; i < precision; i++) {
  25. let t = i / precision;
  26. let p = new Point(0, 0);
  27. result.push(p);
  28. for (let j = 0; j < dimersion; j++) {
  29. let temp = 0.0;
  30. for (let k = 0; k < number; k++) {
  31. temp += Math.pow(1 - t, number - k - 1) * (j == 0 ? poss[k].x : poss[k].y) * Math.pow(t, k) * mi[k];
  32. }
  33. j == 0 ? p.x = temp : p.y = temp;
  34. }
  35. p.x = this.toDecimal(p.x);
  36. p.y = this.toDecimal(p.y);
  37. }
  38. return result;
  39. }

  部分代码如下;

  1. addPoint(p) {
  2. if (this.line.points.length >= 1) {
  3. let last_point = this.line.points[this.line.points.length - 1]
  4. let distance = this.z_distance(p, last_point);
  5. if (distance < 10) {
  6. return;
  7. }
  8. }
  9. if (this.line.points.length == 0) {
  10. this.begin = p;
  11. p.isControl = true;
  12. this.pushPoint(p);
  13. } else {
  14. this.middle = p;
  15. let controlPs = this.computeControlPoints(this.k, this.begin, this.middle, null);
  16. this.pushPoint(controlPs.first);
  17. this.pushPoint(p);
  18. p.isControl = true;
  19. this.begin = this.middle;
  20. }
  21. }

addPoint

  1. draw(isUp = false) {
  2. this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  3. this.ctx.strokeStyle = "rgba(255,20,87,1)";
  4. //绘制不包含this.line的线条
  5. this.pointLines.forEach((line, index) => {
  6. let points = line.points;
  7. this.ctx.beginPath();
  8. this.ctx.ellipse(points[0].x - 1.5, points[0].y, 6, 3, Math.PI / 4, 0, Math.PI * 2);
  9. this.ctx.fill();
  10. this.ctx.beginPath();
  11. this.ctx.moveTo(points[0].x, points[0].y);
  12. let lastW = line.lineWidth;
  13. this.ctx.lineWidth = line.lineWidth;
  14. this.ctx.lineJoin = "round";
  15. this.ctx.lineCap = "round";
  16. let minLineW = line.lineWidth / 4;
  17. let isChangeW = false;
  18. let changeWidthCount = line.changeWidthCount;
  19. for (let i = 1; i <= points.length; i++) {
  20. if (i == points.length) {
  21. this.ctx.stroke();
  22. break;
  23. }
  24. if (i > points.length - changeWidthCount) {
  25. if (!isChangeW) {
  26. this.ctx.stroke();//将之前的线条不变的path绘制完
  27. isChangeW = true;
  28. if (i > 1 && points[i - 1].isControl)
  29. continue;
  30. }
  31. let w = (lastW - minLineW) / changeWidthCount * (points.length - i) + minLineW;
  32. points[i - 1].lineWidth = w;
  33. this.ctx.beginPath();//为了开启新的路径 否则每次stroke 都会把之前的路径在描一遍
  34. // this.ctx.strokeStyle = "rgba("+Math.random()*255+","+Math.random()*255+","+Math.random()*255+",1)";
  35. this.ctx.lineWidth = w;
  36. this.ctx.moveTo(points[i - 1].x, points[i - 1].y);//移动到之前的点
  37. this.ctx.lineTo(points[i].x, points[i].y);
  38. this.ctx.stroke();//将之前的线条不变的path绘制完
  39. } else {
  40. if (points[i].isControl && points[i + 1]) {
  41. this.ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
  42. } else if (i >= 1 && points[i - 1].isControl) {//上一个是控制点 当前点已经被绘制
  43. } else
  44. this.ctx.lineTo(points[i].x, points[i].y);
  45. }
  46. }
  47. })
  48. //绘制this.line线条
  49. let points;
  50. if (isUp)
  51. points = this.line.points;
  52. else
  53. points = this.line.points.clone();
  54. //当前绘制的线条最后几个补点 贝塞尔方式增加点
  55. let count = 0;
  56. let insertCount = 0;
  57. let i = points.length - 1;
  58. let endPoint = points[i];
  59. let controlPoint;
  60. let startPoint;
  61. while (i >= 0) {
  62. if (points[i].isControl == true) {
  63. controlPoint = points[i];
  64. count++;
  65. } else {
  66. startPoint = points[i];
  67. }
  68. if (startPoint && controlPoint && endPoint) {//使用贝塞尔计算补点
  69. let dis = this.z_distance(startPoint, controlPoint) + this.z_distance(controlPoint, endPoint);
  70. let insertPoints = this.BezierCalculate([startPoint, controlPoint, endPoint], Math.floor(dis / 6) + 1);
  71. insertPoints.splice(0, 1);
  72. insertCount += insertPoints.length;
  73. var index = i;//插入位置
  74. // 把arr2 变成一个适合splice的数组(包含splice前2个参数的数组)
  75. insertPoints.unshift(index, 1);
  76. Array.prototype.splice.apply(points, insertPoints);
  77. //补完点后
  78. endPoint = startPoint;
  79. startPoint = null;
  80. }
  81. if (count >= 6)
  82. break;
  83. i--;
  84. }
  85. //确定最后线宽变化的点数
  86. let changeWidthCount = count + insertCount;
  87. if (isUp)
  88. this.line.changeWidthCount = changeWidthCount;
  89. //制造椭圆头
  90. this.ctx.fillStyle = "rgba(255,20,87,1)"
  91. this.ctx.beginPath();
  92. this.ctx.ellipse(points[0].x - 1.5, points[0].y, 6, 3, Math.PI / 4, 0, Math.PI * 2);
  93. this.ctx.fill();
  94. this.ctx.beginPath();
  95. this.ctx.moveTo(points[0].x, points[0].y);
  96. let lastW = this.line.lineWidth;
  97. this.ctx.lineWidth = this.line.lineWidth;
  98. this.ctx.lineJoin = "round";
  99. this.ctx.lineCap = "round";
  100. let minLineW = this.line.lineWidth / 4;
  101. let isChangeW = false;
  102. for (let i = 1; i <= points.length; i++) {
  103. if (i == points.length) {
  104. this.ctx.stroke();
  105. break;
  106. }
  107. //最后的一些点线宽变细
  108. if (i > points.length - changeWidthCount) {
  109. if (!isChangeW) {
  110. this.ctx.stroke();//将之前的线条不变的path绘制完
  111. isChangeW = true;
  112. if (i > 1 && points[i - 1].isControl)
  113. continue;
  114. }
  115. //计算线宽
  116. let w = (lastW - minLineW) / changeWidthCount * (points.length - i) + minLineW;
  117. points[i - 1].lineWidth = w;
  118. this.ctx.beginPath();//为了开启新的路径 否则每次stroke 都会把之前的路径在描一遍
  119. // this.ctx.strokeStyle = "rgba(" + Math.random() * 255 + "," + Math.random() * 255 + "," + Math.random() * 255 + ",0.5)";
  120. this.ctx.lineWidth = w;
  121. this.ctx.moveTo(points[i - 1].x, points[i - 1].y);//移动到之前的点
  122. this.ctx.lineTo(points[i].x, points[i].y);
  123. this.ctx.stroke();//将之前的线条不变的path绘制完
  124. } else {
  125. if (points[i].isControl && points[i + 1]) {
  126. this.ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
  127. } else if (i >= 1 && points[i - 1].isControl) {//上一个是控制点 当前点已经被绘制
  128. } else
  129. this.ctx.lineTo(points[i].x, points[i].y);
  130. }
  131. }
  132. }

draw

动手试试:拖拽写字即可 

 

相关文章:

平滑曲线

实现蜡笔荧光笔效果

实现笔锋效果

画笔性能优化

清除canvas画布内容–点擦除+线擦除

版权声明:本文为fangsmile原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/fangsmile/p/14324460.html