canvas绘图学习 - 学写一个字

内容借鉴慕课网 学写一个字,老师@liuyubobobo讲的不错,值得学习。

学一个技术,最重要的就是练。我自己还是个没啥想法的人,那就借鉴别人的想法,跟着老师学习,模仿大神。

本练习目标是在 PC 和移动端完成一个手写板,以此来练习 canvas 的基础知识。

绘制米字格

首先要绘制米字格,第一步当然是在 html 中添加 canvas:

<canvas id="canvas">您的浏览器不支持canvas</canvas>

然后定义相应的样式。

注意不推荐在 CSS 中定义 canvas 的大小,因为 CSS 定义的 canvas 大小只是它在 html 文档中显示的大小,而不是具体的 canvas 的分辨率。如果想要定义分辨率的话,通常要定义 width 和 height,在 JS 中定义会更好。

进行一段绘制的时候,可以将代码进行封装,另外,如果进行了状态设置,为了不然设置的状态影响到我们其他部分的绘制,要使用 context.save();context.restore(); 这样就不会影响其他部分的状态。

代码如下:

let canvasWidth;
let canvasHeight = (canvasWidth = 800);
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// 绘制米字格:
function drawGrid() {
  context.save();
  context.strokeStyle = "rgb(230,11,9)";
  // 绘制外框
  context.beginPath();
  context.moveTo(3, 3);
  context.lineTo(canvasWidth - 3, 3);
  context.lineTo(canvasWidth - 3, canvasHeight - 3);
  context.lineTo(3, canvasHeight - 3);
  context.closePath();
  context.lineWidth = 6;
  context.stroke();

  // 绘制米字
  context.beginPath();
  context.moveTo(0, 0);
  context.lineTo(canvasWidth, canvasHeight);
  context.moveTo(canvasWidth, 0);
  context.lineTo(0, canvasHeight);
  context.moveTo(canvasWidth / 2, 0);
  context.lineTo(canvasWidth / 2, canvasHeight);
  context.moveTo(0, canvasHeight / 2);
  context.lineTo(canvasWidth, canvasHeight / 2);
  context.lineWidth = 1;
  context.stroke();
  context.restore();
}

鼠标响应

我们的目的是要在 PC 端用鼠标来写字,那我们就需要对鼠标的时间做一个响应。

分别有 onmousedown, onmouseup, onmouseout, onmousemove。我们对每一个事件做响应的操作。

注: e.preventDefault(); 组织浏览器默认动作。(虽然在这个练习的鼠标事件处并没有什么用,但是为了规范写上好一些)

坐标转换

画布位置的屏幕和鼠标点击的屏幕位置需要调整。
我们获取鼠标给回来的文档坐标,然后减去 canvas 的 left 和 top 即可。

使用 canvas.getBoundingClientRect() 获取 canvas 距离上边缘和左边缘的距离即可。

通过对象进行数据传递

鼠标绘制实现

在鼠标移动的时候,利用循环在短时间绘从上一次的位置到当前位置绘制直线。

粗线条问题:如果线条过粗,绘制的直线就变成了一个个矩形,会导致绘制的线条有毛边。

利用 context.lineCap = ‘round’ 以及 context.lineJoin = ‘round’ 使得线条更加自然。

运笔速度对画笔影响

类似一条线的绘制,从初始到终止位置有距离 S 和时间 T,我们通过计算出速度 V 来改变笔画的速度。速度越快画笔越细。

由于速度的变化其实很快,带来的数值变化也非常的大,所以我们需要记录 lastLineWidth 来使得画笔的宽度变化变得更平滑一些。

代码如下:

// 鼠标速度太快导致不平滑,利用之前的信息做过度
if (lastLineWidth === -1) {
  return resultLineWidth;
} else {
  return lastLineWidth * (7 / 10) + resultLineWidth * (1 - 7 / 10);
}

完整代码

如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>学写一个字</title>
    <meta
      name="viewport"
      content="width=device-width,height = device-height,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no"
    />
    <script src="../../jquery-3.3.1/jquery-3.3.1.min.js"></script>
    <link rel="stylesheet" href="css/main.css" />
  </head>
  <body>
    <canvas id="canvas">
      您的浏览器不支持canvas
    </canvas>
    <div id="controller">
      <div id="black_btn" class="color_btn color_btn_selected"></div>
      <div id="blue_btn" class="color_btn"></div>
      <div id="green_btn" class="color_btn"></div>
      <div id="red_btn" class="color_btn"></div>
      <div id="orange_btn" class="color_btn"></div>
      <div id="yellow_btn" class="color_btn"></div>
      <div id="clear_btn" class="op_btn">清除</div>
      <div class="clearFix"></div>
    </div>
    <script src="js/main.js"></script>
  </body>
</html>
#canvas {
  display: block;
  margin: 0 auto;
  border: 1px solid #aaa;
}

#controller {
  margin: 0 auto;
}

.op_btn {
  float: right;
  margin: 10px 0 0 10px;
  border: 2px solid #aaa;
  width: 80px;
  height: 40px;
  line-height: 40px;
  font-size: 20px;
  text-align: center;
  -webkit-border-radius: 5px 5px;
  -moz-border-radius: 5px 5px;
  border-radius: 5px 5px;
  cursor: pointer;
  background-color: white;
  font-weight: bold;
  font-family: "Microsoft Sans Serif";
}

.op_btn:hover {
  background-color: #def;
}

.clearFix {
  clear: both;
}

.color_btn {
  float: left;
  margin: 10px 10px 0 0;
  border: 5px solid white;
  width: 40px;
  height: 40px;
  border-radius: 5px 5px;
  cursor: pointer;
}

.color_btn:hover {
  border: 5px solid violet;
}

.color_btn_selected {
  border: 5px solid blueviolet;
}

#black_btn {
  background-color: black;
}

#blue_btn {
  background-color: blue;
}

#green_btn {
  background-color: green;
}

#red_btn {
  background-color: red;
}

#orange_btn {
  background-color: orange;
}

#yellow_btn {
  background-color: yellow;
}
let canvasWidth;
let canvasHeight = (canvasWidth = Math.min(800, $(window).width() - 20));

let strokeColor = "black";
let isMouseDown = false;
let lastLoc = { x: 0, y: 0 };
let lastTimeStamp = 0;
let lastLineWidth = -1;

let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");

canvas.width = canvasWidth;
canvas.height = canvasHeight;

function windowToCanvas(x, y) {
  // 窗口坐标系转换为canvas坐标系
  let bbox = canvas.getBoundingClientRect();
  return { x: Math.round(x - bbox.left), y: Math.round(y - bbox.top) };
}

// 适配底部宽度
$("#controller").css("width", canvasWidth + "px");

drawGrid();

$("#clear_btn").click(function() {
  context.clearRect(0, 0, canvasWidth, canvasHeight);
  drawGrid();
});

$(".color_btn").click(function(e) {
  $(".color_btn").removeClass("color_btn_selected");
  $(this).addClass("color_btn_selected");
  strokeColor = $(this).css("background-color");
});

function beginStroke(point) {
  isMouseDown = true;
  lastLoc = windowToCanvas(point.x, point.y);
  lastTimeStamp = new Date().getTime();
}

function endStroke() {
  isMouseDown = false;
}

function moveStroke(point) {
  // console.log('mouse move')
  let curLoc = windowToCanvas(point.x, point.y);
  let curTimeStamp = new Date().getTime();
  let s = calcDistance(curLoc, lastLoc); // 路程
  let t = curTimeStamp - lastTimeStamp; // 时间

  let lineWidth = calcLineWidth(t, s);

  // TODO: draw
  context.beginPath();
  context.moveTo(lastLoc.x, lastLoc.y);
  context.lineTo(curLoc.x, curLoc.y);
  context.strokeStyle = strokeColor;
  context.lineWidth = lineWidth;
  context.lineCap = "round"; // 解决线变粗导致的线条衔接问题
  context.lineJoin = "round"; // 线条衔接问题
  context.stroke();

  lastLoc = curLoc;
  lastTimeStamp = curTimeStamp;
  lastLineWidth = lineWidth;
}

canvas.onmousedown = function(e) {
  e.preventDefault();
  beginStroke({ x: e.clientX, y: e.clientY });
};
canvas.onmousemove = function(e) {
  e.preventDefault();
  if (isMouseDown) {
    moveStroke({ x: e.clientX, y: e.clientY });
  }
};
canvas.onmouseup = function(e) {
  e.preventDefault();
  endStroke();
};
canvas.onmouseout = function(e) {
  e.preventDefault();
  endStroke();
};

canvas.addEventListener("touchstart", function(e) {
  e.preventDefault();
  touch = e.touches[0];
  beginStroke({ x: touch.pageX, y: touch.pageY });
});
canvas.addEventListener("touchmove", function(e) {
  e.preventDefault();
  if (isMouseDown) {
    touch = e.touches[0];
    moveStroke({ x: touch.pageX, y: touch.pageY });
  }
});
canvas.addEventListener("touchend", function(e) {
  e.preventDefault();
  endStroke();
});

let maxLineWidth = 30;
let minLineWidth = 1;
let maxStrokeV = 10;
let minStrokeV = 0.1;

function calcLineWidth(t, s) {
  let v = s / t;
  let resultLineWidth;
  if (v <= minStrokeV) {
    resultLineWidth = maxLineWidth;
  } else if (v >= maxStrokeV) {
    resultLineWidth = minLineWidth;
  } else {
    resultLineWidth =
      maxLineWidth -
      ((v - minStrokeV) / (maxStrokeV - minStrokeV)) *
        (maxLineWidth - minLineWidth);
  }
  console.log(resultLineWidth);
  // 鼠标速度太快导致不平滑,利用之前的信息做过度
  if (lastLineWidth === -1) {
    return resultLineWidth;
  } else {
    return lastLineWidth * (7 / 10) + resultLineWidth * (1 - 7 / 10);
  }
}

function calcDistance(loc1, loc2) {
  // 计算两点距离
  return Math.sqrt(
    (loc1.x - loc2.x) * (loc1.x - loc2.x) +
      (loc1.y - loc2.y) * (loc1.y - loc2.y)
  );
}

function drawGrid() {
  context.save();
  context.strokeStyle = "rgb(230,11,9)";
  // 绘制外框
  context.beginPath();
  context.moveTo(3, 3);
  context.lineTo(canvasWidth - 3, 3);
  context.lineTo(canvasWidth - 3, canvasHeight - 3);
  context.lineTo(3, canvasHeight - 3);
  context.closePath();
  context.lineWidth = 6;
  context.stroke();

  // 绘制米字
  context.beginPath();
  context.moveTo(0, 0);
  context.lineTo(canvasWidth, canvasHeight);
  context.moveTo(canvasWidth, 0);
  context.lineTo(0, canvasHeight);
  context.moveTo(canvasWidth / 2, 0);
  context.lineTo(canvasWidth / 2, canvasHeight);
  context.moveTo(0, canvasHeight / 2);
  context.lineTo(canvasWidth, canvasHeight / 2);
  context.lineWidth = 1;
  context.stroke();
  context.restore();
}

放在最后

如果您喜欢我的文章,拜托点赞+收藏+关注,博主会根据大家喜好来推出相关系列文章~

更多精彩内容也可以访问我的博客Aelous-BLog

猜你喜欢

转载自blog.csdn.net/Aelous_dp/article/details/107480912