canvas进阶之特殊形状绘制

1,133 阅读2分钟

前言

在阅读完 canvas - 基础了解 中后,想必大家对 canvas 有了一定的认知,那么接下来,咱们就玩点稍微有点意思的东西....

创建弧/曲线

  • 创建介于两个切线之间的弧/曲线

image.png

var c = document.getElementById('canvas');
var ctx = c.getContext('2d');

ctx.strokeStyle = 'red';
ctx.moveTo(20, 20); // 创建开始点
ctx.arcTo(40, 20, 40, 50, 20); // 创建弧
ctx.stroke(); // 进行绘制

API

  • 三次贝塞尔曲线:有两个控制点

    • bezierCurveTo(控制点1x,控制点1y,控制点2x,控制点2y,结束点x, 结束点y)

image.png

  • 二次贝塞尔曲线:它仅有有一个控制点

image.png

  • 二次贝塞尔曲线:它仅有有一个控制点

image.png

bezierCurveTo - demo

image.png

//控制点
function draw(id) {
  var canvas = document.getElementById(id);
  var ctx = canvas.getContext('2d');
  
  ctx.fillStyle = '#eeeeef';
  ctx.fillRect(0, 0, 300, 400);
  
  var dx = 150;
  var dy = 150;
  var s = 100;
  ctx.beginPath();
  ctx.fillStyle = 'rgb(100,255,100)';
  var x = Math.sin(0);
  var y = Math.cos(0);
  var dig = (Math.PI / 15) * 11;
  ctx.moveTo(dx, dy);
  for (var i = 0; i < 30; i++) {
    x = Math.sin(i * dig);
    y = Math.cos(i * dig);
    ctx.bezierCurveTo(dx + x * s, dy + y * s - 100, dx + x * s + 100, dy + y * s, dx + x * s, dy + y * s);
  }
  ctx.closePath();
  ctx.fill();
  ctx.stroke();
}

bezierCurveTo + quadraticCurveTo - demo

image.png

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

// 画天空
var g = context.createRadialGradient(
  canvas.width / 2,
  canvas.height / 2,
  0,
  canvas.width / 2,
  canvas.height,
  canvas.height
);
g.addColorStop(0.0, '#035');
g.addColorStop(1.0, 'black');
context.fillStyle = g;
context.fillRect(0, 0, canvas.width, canvas.height);

/****** 画星星 ******/
for (var i = 0; i < 100; i++) {
  var R = parseInt(Math.random() * 10);
  var x = parseInt(Math.random() * canvas.width) + 5;
  var y = parseInt(Math.random() * canvas.height * 0.6) + 5;
  var rot = parseInt(Math.random() * 50);
  drawStar(context, R, x, y, rot, '#fb3');
}

//半径小,半径大,x,y,线宽,角度,颜色,类型
function drawStar(ctx, R, x, y, rot, color) {
  ctx.save();
  ctx.translate(x, y);
  ctx.scale(R, R);
  ctx.rotate((rot / 180) * Math.PI);
  starPath(ctx, color);
  ctx.restore();
}

function starPath(ctx, color) {
  ctx.beginPath();
  for (var i = 0; i <= 5; i++) {
    ctx.lineTo(Math.cos(((18 + i * 72) / 180) * Math.PI), -Math.sin(((18 + i * 72) / 180) * Math.PI));
    ctx.lineTo(Math.cos(((54 + i * 72) / 180) * Math.PI) * 0.5, -Math.sin(((54 + i * 72) / 180) * Math.PI) * 0.5);
    ctx.fillStyle = color;
    ctx.fill();
  }
}

/****** 画月亮 *******/
drawMoon(context, 100, 100, 50);

function drawMoon(ctx, x, y, r) {
  ctx.beginPath();
  ctx.save();
  ctx.translate(x, y);
  ctx.scale(r, r);
  drawMoonPath(ctx, r);
  ctx.restore();
}

function drawMoonPath(ctx) {
  // 画半圆
  ctx.arc(0, 0, 1, 0.5 * Math.PI, 1.5 * Math.PI, true);
  ctx.moveTo(0, -1);
  ctx.quadraticCurveTo(1, 0, 0, 1);
  ctx.closePath();
  ctx.fillStyle = '#fb3';
  ctx.fill();
}

/****** 画陆地 *******/
drawLand(context);

function drawLand(ctx) {
  ctx.beginPath();
  ctx.moveTo(0, canvas.height * 0.8);
  ctx.bezierCurveTo(300, canvas.height * 0.6, 600, canvas.height * 0.9, canvas.width, canvas.height * 0.8);
  ctx.lineTo(canvas.width, canvas.height);
  // ctx.lineTo(0, canvas.height);
  var g = ctx.createLinearGradient(0, canvas.height, 0, canvas.height * 0.7);
  g.addColorStop(0.0, '#030');
  g.addColorStop(1.0, '#580');
  ctx.fillStyle = g;
  ctx.fill();
}

非零环绕规则

路径一正一反,则求和为0

  • canvas在进行填充的时候是否要进行填充的判断依据.用来判断内侧和外侧的方式
  • 在判断填充的区域拉一条线出来,拉到图形的外面,这条拉出来的线就是辅助线。
  • 判断绘制的线是否是从辅助线的左边穿过到辅助线的右边,此时这种穿过的方式记录为+1
  • 如果是从辅助线的右边穿到辅助线的左边,就记做-1.
  • 最后将所有记录的数字进行求和,如果求和的结果为0,代表这块区域不要填充,否则,必须填充

image.png

剪纸效果

  • 本例中,两个圆的路径一顺时针一逆时针,求和为0,不填充

image.png

var canvas = document.getElementById('canvas');
canvas.width = 400;
canvas.height = 400;

var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(200, 200, 100, 0, Math.PI * 2);
ctx.arc(200, 200, 50, 0, Math.PI * 2, true);
// ctx.closePath();

ctx.fillStyle = '#058';
ctx.shadowColor = 'gray';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 10;
ctx.fill();