canvas 基础入门

是什么

Canvas(画布)是在 HTML5 中新增的标签用于在网页实时生成图像,可以操作图像内容,是一个可以用 JavaScript 操作的位图(bitmap)。

canvas 的应用领域

游戏:canvas 在基于 Web 的图像显示方面比 Flash 更加立体、更加精巧,canvas 游戏在流畅度和跨平台方面更优秀。

可视化的库:Echart

banner 广告:Canvas 实现动态的广告效果非常合适

图形编辑器:后续 PhotoShop 能够 100%基于 Web 实现

微信读书、腾讯文档均是通过 canvas 实现

使用

  1. 创建一个标签

  2. 获取 canvas 元素对应的 DOM 对象,这是一个 Canvas 对象

  3. 调用 Canvas 对象的 getContext()方法,该方法返回一个 CanvasRenderingContext2D 对象,该对象即可绘制图形

  4. 调用 CanvasRenderingContext2D 对象的方法绘图

创建坐标系

在 canvas 中默认坐标系在左上角(X 轴正方向向右、Y 轴正方向向下),可是有的时候需要变换坐标系才能更方便的实现所需的效果,此时需要变换坐标系,canvas 提供了以下几种变换坐标系的方式:

  1. translate(dx,dy):平移坐标系。相当于把原来位于(0,0)位置的坐标原点平移到(dx、dy)点。

  2. rotate(angle):旋转坐标系。该方法控制坐标系统顺时针旋转 angle 弧度。

  3. scale(sx,sy):缩放坐标系。该方法控制坐标系统水平方向上缩放 sx,垂直方向上缩放 sy。

  4. transform(a,b,c,d,e,f):允许缩放、旋转、移动并倾斜当前的环境坐标系,其中 a 表示水平缩放、b 表示水平切斜、c 表示垂直切斜、d 表示垂直缩放、e 表示水平移动、f 表示垂直移动。

function main() {
  const canvas = document.getElementById("canvasId");
  const ctx = canvas.getContext("2d");
  ctx.lineWidth = 4;
  // 默认
  ctx.save();
  ctx.strokeStyle = "#F00";
  drawCoordiante(ctx);
  ctx.restore();

  // 平移
  ctx.save();
  ctx.translate(150, 150);
  ctx.strokeStyle = "#0F0";
  drawCoordiante(ctx);
  ctx.restore();

  // 旋转
  ctx.save();
  ctx.translate(300, 300);
  ctx.rotate(-Math.PI / 2);
  ctx.strokeStyle = "#00F";
  drawCoordiante(ctx);
  ctx.restore();

  // 缩放
  ctx.save();
  ctx.translate(400, 400);
  ctx.rotate(-Math.PI / 2);
  ctx.scale(0.5, 0.5);
  ctx.strokeStyle = "#000";
  drawCoordiante(ctx);
  ctx.restore();
}

function drawCoordiante(ctx) {
  ctx.beginPath();
  ctx.moveTo(0, 0);
  ctx.lineTo(120, 0);
  ctx.moveTo(0, 0);
  ctx.lineTo(0, 80);
  ctx.closePath();
  ctx.stroke();
}

main();

图形绘制

根据坐标系绘制内容

// 直线
function drawLine(ctx, startX, startY, endX, endY) {
  ctx.moveTo(startX, startY);
  ctx.lineTo(endX, endY);
  ctx.stroke();
}
// 圆弧
function drawCircle(ctx, x, y, R, startAngle, endAngle) {
  ctx.arc(x, y, R, startAngle, endAngle);
  ctx.stroke();
}
// 贝济埃曲线
function drawBezierCurve(ctx, cpX1, cpY1, cpX, cpY2, endX, endY) {
  ctx.bezierCurveTo(cpX1, cpY1, cpX, cpY2, endX, endY);
  ctx.stroke();
}
// 二次曲线
function drawQuadraticCurve(ctx, cpX, cpY, endX, endY) {
  ctx.quadraticCurveTo(cpX, cpY, endX, endY);
  ctx.stroke();
}
// 填充矩形
function drawFillRect(ctx, x, y, width, height) {
  ctx.fillRect(x, y, width, height);
}
// 边框矩形
function drawStrokeRect(ctx, x, y, width, height) {
  ctx.strokeRect(x, y, width, height);
}
// 填充字符串
function drawFillText(ctx, text, x, y) {
  ctx.fillText(text, x, y);
}
// 边框字符串
function drawStrokeText(ctx, text, x, y) {
  ctx.strokeText(text, x, y);
}
// 利用路径绘制
function drawFigureByPath(ctx) {
  ctx.beginPath();
  ctx.moveTo(100, 400);
  ctx.lineTo(200, 450);
  ctx.lineTo(150, 480);
  ctx.closePath();
  ctx.fill();
}
// 初始化
function main() {
  const canvas = document.getElementById("canvasId");
  const ctx = canvas.getContext("2d");
  ctx.lineWidth = 2;
  ctx.strokeStyle = "#F00";
  ctx.fillStyle = "#F00";
  ctx.font = "normal 50px 宋体";
  drawLine(ctx, 50, 10, 150, 10);
  ctx.moveTo(150, 100);
  drawCircle(ctx, 100, 100, 50, 0, Math.PI);
  ctx.moveTo(300, 100);
  drawCircle(ctx, 250, 100, 50, 0, Math.PI / 2);
  ctx.moveTo(350, 150);
  drawBezierCurve(ctx, 200, 200, 450, 250, 300, 300);
  ctx.moveTo(50, 250);
  drawQuadraticCurve(ctx, 50, 400, 80, 400);
  drawFillRect(ctx, 100, 300, 100, 50);
  drawStrokeRect(ctx, 300, 300, 100, 50);
  drawFillText(ctx, "I", 100, 400);
  drawStrokeText(ctx, "I", 300, 400);
  drawFigureByPath(ctx);
}

main();

填充颜色

利用 canvas 绘制图形时势必要上点颜料,通过设置 fillStyle 属性即可设置对应的颜料,对于颜料值主要有以下四种:纯颜色、线性渐变颜色、径向渐变颜色、位图。

// 纯颜色
function useColorFill(ctx) {
  ctx.save();
  ctx.fillStyle = "#F00";
  ctx.fillRect(10, 10, 100, 100);
  ctx.restore();
}

// 线性渐变颜色
function useLinearGradientFill(ctx) {
  ctx.save();
  const lg = ctx.createLinearGradient(110, 10, 210, 10);
  lg.addColorStop(0.2, "#F00");
  lg.addColorStop(0.5, "#0F0");
  lg.addColorStop(0.9, "#00F");
  ctx.fillStyle = lg;
  ctx.fillRect(120, 10, 100, 100);
  ctx.restore();
}

// 径向渐变颜色
function useRadialGradientFill(ctx) {
  ctx.save();
  const lg = ctx.createRadialGradient(260, 60, 10, 260, 60, 60);
  lg.addColorStop(0.2, "#F00");
  lg.addColorStop(0.5, "#0F0");
  lg.addColorStop(0.9, "#00F");
  ctx.fillStyle = lg;
  ctx.fillRect(230, 10, 100, 100);
  ctx.restore();
}

// 位图填充
function useImageFill(ctx) {
  ctx.save();
  const image = new Image();
  image.src = "https://source.unsplash.com/Xm9-vA_bhm0/300x500";
  image.onload = function () {
    // 创建位图填充
    const imgPattern = ctx.createPattern(image, "repeat");
    ctx.fillStyle = imgPattern;
    ctx.fillRect(340, 10, 100, 100);
    ctx.restore();
  };
}

function main() {
  const canvas = document.getElementById("canvasId");
  const ctx = canvas.getContext("2d");
  useRadialGradientFill(ctx);
  useImageFill(ctx);
}
main();

填充外部图像

有的时候需要引入外部图片,然后对外部图片进行像素级别的处理,最后进行保存。

  1. 绘制图像:drawImage

  2. 取得图像数据:getImageData

  3. 将修改后的数据重新填充到 Canvas 中:putImageData

  4. 输出位图:toDataURL

function main() {
  const canvas = document.getElementById("canvasId");
  const ctx = canvas.getContext("2d");
  const image = document.getElementById("image");
  // 绘制图像
  ctx.drawImage(image, 0, 0);
  // 获取图像数据
  const imageData = ctx.getImageData(0, 0, image.width, image.height);
  const data = imageData.data;
  for (let i = 0, len = data.length; i < len; i += 4) {
    const red = data[i];
    const green = data[i + 1];
    const blue = data[i + 2];

    const average = Math.floor((red + green + blue) / 3);

    data[i] = average;
    data[i + 1] = average;
    data[i + 2] = average;
  }

  imageData.data = data;
  ctx.putImageData(imageData, 0, 0);
  document.getElementById("result").src = canvas.toDataURL("image/png");
}

实战

<canvas id="canvasId" width="1000px" height="600px"></canvas>
<script>
  function main() {
    const canvas = document.getElementById("canvasId");
    const ctx = canvas.getContext("2d");
    ctx.lineWidth = 4;
    ctx.strokeStyle = "#000";
    ctx.fillStyle = "#ffd8e1";
    ctx.translate(320, 320);
    drawPigEar(ctx);
    ctx.save();
    ctx.rotate(Math.PI / 2);
    drawPigEar(ctx);
    ctx.restore();
    drawPigFace(ctx);
    ctx.save();
    ctx.translate(-100, -100);
    drawPigEye(ctx);
    ctx.restore();
    ctx.save();
    ctx.translate(100, -100);
    drawPigEye(ctx);
    ctx.restore();
    ctx.save();
    ctx.translate(0, 60);
    drawPigNose(ctx);
    ctx.restore();
  }

  function drawPigEar(ctx) {
    ctx.save();
    ctx.beginPath();
    ctx.arc(-250, 0, 250, 0, -Math.PI / 2, true);
    ctx.arc(0, -250, 250, -Math.PI, Math.PI / 2, true);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
    ctx.restore();
  }

  function drawPigFace(ctx) {
    ctx.save();
    ctx.beginPath();
    ctx.arc(0, 0, 250, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
  }

  function drawPigEye(ctx) {
    ctx.save();
    ctx.fillStyle = "#000";
    ctx.beginPath();
    ctx.arc(0, 0, 20, 0, Math.PI * 2);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  function drawPigNose(ctx) {
    ctx.save();
    ctx.fillStyle = "#fca7aa";
    ctx.beginPath();
    ctx.ellipse(0, 0, 150, 100, 0, 0, Math.PI * 2);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
    ctx.save();
    ctx.translate(-60, 0);
    drawPigNostrils(ctx);
    ctx.restore();
    ctx.save();
    ctx.translate(60, 0);
    drawPigNostrils(ctx);
    ctx.restore();
    ctx.restore();
  }

  function drawPigNostrils(ctx) {
    ctx.save();
    ctx.fillStyle = "#b55151";
    ctx.beginPath();
    ctx.ellipse(0, 0, 40, 60, 0, 0, Math.PI * 2);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
    ctx.restore();
  }

  main();
</script>
```
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容