Canvas 超全教程

一、简介

<canvas>是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素.例如,它可以用于绘制图表、制作图片构图或者制作简单的(以及不那么简单的)动画.

<canvas> 最早由Apple引入WebKit,用于Mac OS X 的 Dashboard,随后被各个浏览器实现。如今,所有主流的浏览器都支持它。

二、兼容性处理

1. 替换文本

<canvas>元素与<img>标签的不同之处在于,就像<video><audio>,或者<picture>元素一样,很容易定义一些替代内容。由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者文本浏览器不支持HTML元素"canvas",在这些浏览器上你应该总是能展示替代内容。

这非常简单:我们只是在<canvas>标签中提供了替换内容。不支持<canvas>的浏览器将会忽略容器并在其中渲染后备内容。而支持<canvas>的浏览器将会忽略在容器中包含的内容,并且只是正常渲染canvas。

举个例子,我们可以提供对canvas内容的文字描述或者是提供动态生成内容相对应的静态图片,如下所示:

<canvas id="stockGraph" width="150" height="150">
  current stock price: $3.15 +0.15
</canvas>

<canvas id="clock" width="150" height="150">
  <img src="images/clock.png" width="150" height="150" alt=""/>
</canvas>

2. 检查支持性

替换内容是用于在不支持 <canvas> 标签的浏览器中展示的。通过简单的测试getContext()方法的存在,脚本可以检查编程支持性。上面的代码片段现在变成了这个样子:

var canvas = document.getElementById('tutorial');

if (canvas.getContext){
  var ctx = canvas.getContext('2d');
  // drawing code here
} else {
  // canvas-unsupported code here
}

三、模版骨架

这里的是一个最简单的模板,我们之后就可以把它作为之后的例子的起点。

<html>
  <head>
    <title>Canvas tutorial</title>
    <style type="text/css">
      canvas { border: 1px solid black; }
    </style>
  </head>
  <body onload="draw();">
    <canvas id="tutorial" width="150" height="150"></canvas>

    <script type="text/javascript">
      function draw(){
        var canvas = document.getElementById('tutorial');
        if (canvas.getContext){
          var ctx = canvas.getContext('2d');
          // drawing code here
        } else {
          // canvas-unsupported code here
        }
      }
    </script>
  </body>
</html>

](https://upload-images.jianshu.io/upload_images/4573742-5cd1f3cb46481846.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

上面的脚本中包含一个叫做draw()的函数,当页面加载结束的时候就会执行这个函数。通过使用在文档上加载事件来完成。只要页面加载结束,这个函数,或者像是这个的,同样可以使用 window.setTimeout(), window.setInterval(),或者其他任何事件处理程序来调用。

四、绘制形状

1. 栅格

在我们开始画图之前,我们需要了解一下画布栅格(canvas grid)以及坐标空间。上一页中的HTML模板中有个宽150px, 高150px的canvas元素。如右图所示,canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。在课程的最后我们会平移原点到不同的坐标上,旋转网格以及缩放。现在我们还是使用原来的设置。

image

2. 渲染上下文(The rendering context)

<canvas> 元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。我们将会将注意力放在2D渲染上下文中。其他种类的上下文也许提供了不同种类的渲染方式;比如, WebGL 使用了基于OpenGL ES的3D上下文 ("experimental-webgl") 。
canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas> 元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()只有一个参数,上下文的格式。对于2D图像而言,如本教程,你可以使用 CanvasRenderingContext2D。

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

3. 绘制矩形

不同于SVG,HTML中的元素canvas只支持一种原生的图形绘制:矩形。所有其他的图形的绘制都至少需要生成一条路径。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。

  • [fillRect(x, y, width, height)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillRect): 绘制一个填充的矩形
  • [strokeRect(x, y, width, height)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/strokeRect): 绘制一个矩形的边框
  • [clearRect(x, y, width, height)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/clearRect):清除指定矩形区域,让清除部分完全透明。
    不同于下一节所要介绍的路径函数(path function),以上的三个函数绘制之后会马上显现在canvas上,即时生效。
  • rect(x, y, width, height) :将一个矩形路径增加到当前路径上。绘制一个左上角坐标为(x,y),宽高为width以及height的矩形。注: 当该方法执行的时候,moveTo()方法自动设置坐标参数(0,0)。也就是说,当前笔触自动重置回默认坐标。

4. 绘制路径

图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤。

  1. 首先,你需要创建路径起始点。
  2. 然后你使用画图命令去画出路径。
  3. 之后你把路径封闭。
  4. 一旦路径生成,你就能通过描边或填充路径区域来渲染图形。

以下是所要用到的函数:

  • beginPath()
    新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
    生成路径的第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。
  • closePath()
    闭合路径之后图形绘制命令又重新指向到上下文中。不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。
  • stroke()
    通过线条来绘制图形轮廓。
  • fill()
    通过填充路径的内容区域生成实心的图形。

注:当你调用fill()函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath()函数。但是调用stroke()时不会自动闭合。

4. 移动笔触

moveTo(x, y):将笔触移动到指定的坐标x以及y上。

5. 线

[lineTo(x, y)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineTo):绘制一条从当前位置到指定x以及y位置的直线。

6. 圆弧

绘制圆弧或者圆,我们使用arc()方法。当然可以使用arcTo(),不过这个的实现并不是那么的可靠,所以我们这里不作介绍。

  • [arc(x, y, radius, startAngle, endAngle, anticlockwise)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/arc): 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针, bool)来生成。

注:arc()函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式: 弧度=(Math.PI/180)*角度。

  • [arcTo(x1, y1, x2, y2, radius)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/arcTo): 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。

7. 二次贝塞尔曲线及三次贝塞尔曲线

  • quadraticCurveTo(cp1x, cp1y, x, y):绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点。
  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y): 绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

下边的图能够很好的描述两者的关系,二次贝塞尔曲线有一个开始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点。

参数x、y在这两个方法中都是结束点坐标。cp1x,cp1y为坐标中的第一个控制点,cp2x,cp2y为坐标中的第二个控制点。

image

8. Path 2D 对象

  • [Path2D()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Path2D/Path2D):Path2D()会返回一个新初始化的Path2D对象(可能将某一个路径作为变量——创建一个它的副本,或者将一个包含SVG path数据的字符串作为变量)。new Path2D(); // 空的Path对象 new Path2D(path); // 克隆Path对象 new Path2D("M10 10 h 80 v 80 h -80 Z"); // 从SVG建立Path对象,这条路径将先移动到点 (M10 10) 然后再水平移动80个单位(h 80),然后下移80个单位 (v 80),接着左移80个单位 (h -80),再回到起点处 (z)。

所有的路径方法比如moveTo, rect, arc或quadraticCurveTo等,如我们前面见过的,都可以在Path2D中使用。

Path2D API 添加了 addPath作为将path结合起来的方法。当你想要从几个元素中来创建对象时,这将会很实用。比如:

  • [Path2D.addPath(path [, transform])](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Path2D/addPath%255D)是 Canvas 2D API 根据指定路径变量添加路径的方法。")​: 添加了一条路径到当前路径(可能添加了一个变换矩阵)。

五、添加样式和色彩

1. 色彩 Colors

  • [fillStyle = color](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillStyle): 设置图形的填充颜色,需是符合 CSS3 颜色值标准 的有效字符串:
ctx.fillStyle = "orange";
  ctx.fillStyle = "#FFA500";
  ctx.fillStyle = "rgb(255,165,0)";
  ctx.fillStyle = "rgba(255,165,0,1)";
  • [strokeStyle = color](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/strokeStyle):设置图形轮廓的颜色。

注: 一旦您设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果你要给每个图形上不同的颜色,你需要重新设置 fillStyle 或 strokeStyle 的值。

2. 透明度 Transparency

  • [globalAlpha = transparencyValue](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalAlpha): 这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。
    因为 strokeStyle 和 fillStyle 属性接受符合 CSS 3 规范的颜色值,那我们可以用下面的写法来设置具有透明度的颜色。
// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";

3. 线型

  • [lineWidth = value](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineWidth): 设置线条宽度。
  • [lineCap = type](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineCap): 设置线条末端样式。
  • [lineJoin = type](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineJoin): 设定线条与线条间接合处的样式。
  • [miterLimit = value](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/miterLimit): 限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度。
  • [getLineDash()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getLineDash): 返回一个包含当前虚线样式,长度为非负偶数的数组。
  • [setLineDash(segments)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/setLineDash): 设置当前虚线样式。
  • [lineDashOffset = value](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineDashOffset): 设置虚线样式的起始偏移量。

4. 渐变 Gradients

  • [createLinearGradient(x1, y1, x2, y2)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createLinearGradient): createLinearGradient 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。
  • [createRadialGradient(x1, y1, r1, x2, y2, r2)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createRadialGradient): createRadialGradient 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。

创建出 canvasGradient 对象后,我们就可以用 addColorStop 方法给它上色了。

  • [gradient.addColorStop(position, color)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasGradient/addColorStop): addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。
var lineargradient = ctx.createLinearGradient(0,0,150,150);
lineargradient.addColorStop(0,'white');
lineargradient.addColorStop(1,'black');

5. 图案样式 Patterns

  • [createPattern(image, type)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createPattern): 该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat。

注意: 用 canvas 对象作为 Image 参数在 Firefox 1.5 (Gecko 1.8) 中是无效的。

var img = new Image();
img.src = 'someimage.png';
var ptrn = ctx.createPattern(img,'repeat');

6. 阴影 Shadows

  • [shadowOffsetX = float](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX)
  • [shadowOffsetY = float](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY)
    shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0。
  • [shadowBlur = float](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/shadowBlur):
    shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0。
  • [shadowColor = color](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/shadowColor): shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。

7. Canvas 填充规则

当我们用到 fill(或者 [clip](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/clip)[isPointinPath](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/isPointInPath) )你可以选择一个填充规则,该填充规则根据某处在路径的外面或者里面来决定该处是否被填充,这对于自己与自己路径相交或者路径被嵌套的时候是有用的。
两个可能的值:

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d'); 
  ctx.beginPath(); 
  ctx.arc(50, 50, 30, 0, Math.PI*2, true);
  ctx.arc(50, 50, 15, 0, Math.PI*2, true);
  ctx.fill("evenodd");
}
image

六、绘制文本

  • [fillText(text, x, y [, maxWidth])](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillText): 在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.
  • [strokeText(text, x, y [, maxWidth])](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/strokeText): 在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.
  • [font = value](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/font): 当前我们用来绘制文本的样式. 这个字符串使用和 CSS[font](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/CSS/font) 属性相同的语法. 默认的字体是 10px sans-serif
  • [textAlign = value](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/textAlign "CanvasRenderingContext2D.textAlign 是 Canvas 2D API 描述绘制文本时,文本的对齐方式的属性。注意,该对齐是基于CanvasRenderingContext2D.fillText方法的x的值。所以如果textAlign="center",那么该文本将画在 x-50%*width。"): 文本对齐选项. 可选的值包括:start, end, left, right or center. 默认值是 start。
  • [textBaseline = value](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/textBaseline): 基线对齐选项. 可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。
  • [direction = value](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/direction): 文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。
  • [measureText()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/measureText): 将返回一个 [TextMetrics](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/TextMetrics)对象的宽度、所在像素,这些体现文本特性的属性。
// 下面的代码段将展示如何测量文本来获得它的宽度:
function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  var text = ctx.measureText("foo"); // TextMetrics object
  text.width; // 16;
}

七、使用图片

引入图像到canvas里需要以下两步基本操作:

  1. 获得一个指向[HTMLImageElement](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement)的对象或者另一个canvas元素的引用作为源,也可以通过提供一个URL的方式来使用图片(参见例子
  2. 使用drawImage()函数将图片绘制到画布上

1. 获得需要绘制的图片

  • [HTMLImageElement](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement): 这些图片是由Image()函数构造出来的,或者任何的[<img>](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/HTML/Element/img)元素
  • [HTMLVideoElement](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLVideoElement): 用一个HTML的 [<video>](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video)元素作为你的图片源,可以从视频中抓取当前帧作为一个图像
  • [ImageBitmap](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/ImageBitmap): 这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其它几种源中生成。
    这些源统一由 [CanvasImageSource](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasImageSource)类型来引用。
    有几种方式可以获取到我们需要在canvas上使用的图片。
    (1) 使用相同页面内的图片
  • [document.images](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Document/images)集合
  • [document.getElementsByTagName()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Document/getElementsByTagName)方法
  • 如果你知道你想使用的指定图片的ID,你可以用[document.getElementById()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Document/getElementById)获得这个图片
    (2) 使用其它域名下的图片
    [HTMLImageElement](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement)上使用crossOrigin属性,你可以请求加载其它域名上的图片。如果图片的服务器允许跨域访问这个图片,那么你可以使用这个图片而不污染canvas,否则,使用这个图片将会污染canvas
    (3) 使用其它 canvas 元素
    和引用页面内的图片类似地,用 [document.getElementsByTagName](https://developer.mozilla.org/en/DOM/document.getElementsByTagName "en/DOM/document.getElementsByTagName")[document.getElementById](https://developer.mozilla.org/en/DOM/document.getElementById "en/DOM/document.getElementById")方法来获取其它 canvas 元素。但你引入的应该是已经准备好的 canvas。
    (4) 由零开始创建图像
    或者我们可以用脚本创建一个新的 [HTMLImageElement](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLImageElement) 对象。要实现这个方法,我们可以使用很方便的Image()构造函数。
var img = new Image();   // 创建img元素
img.onload = function(){
  // 执行drawImage语句
}
img.src = 'myImage.png'; // 设置图片源地址

(5) 通过 data: url 方式嵌入图像
我们还可以通过 data:url 方式来引用图像。Data urls 允许用一串 Base64 编码的字符串的方式来定义一个图片。

img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';

(6) 使用视频帧

function getMyVideo() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    return document.getElementById('myvideo');
  }
}

2. 绘制图片

  • [drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage): 第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。其它8个参数最好是参照右边的图解,前4个是定义图像源的切片位置和大小,后4个则是定义切片的目标显示位置和大小。

SVG图像必须在 <svg> 根指定元素的宽度和高度。

image

3. 控制图像的缩放行为

Gecko 1.9.2 引入了 mozImageSmoothingEnabled 属性,值为 false 时,图像不会平滑地缩放。默认是 true

cx.mozImageSmoothingEnabled = false;

八、变形

1. 状态的保存和恢复

  • [save()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/save): 保存画布(canvas)的所有状态
  • [restore()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/restore): save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

2. 移动

  • translate(x, y): translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如右图所示。

3. 旋转

  • rotate(angle): 这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。

4. 缩放

  • scale(x, y): scale 方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会比缩放图形, 如果比1大会放大图形。默认值为1, 为实际大小。

5. 变形

  • transform(m11, m12, m21, m22, dx, dy): 这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵,在这里我们用下面的矩阵:
m11 m21 dx
m12 m22 dy
0   0   1

如果任意一个参数是无限大,变形矩阵也必须被标记为无限大,否则会抛出异常。

这个函数的参数各自代表如下:

m11:水平方向的缩放

m12:水平方向的倾斜偏移

m21:竖直方向的倾斜偏移

m22:竖直方向的缩放

dx:水平方向的移动

dy:竖直方向的移动

  • setTransform(m11, m12, m21, m22, dx, dy)
  • resetTransform: 重置当前变形为单位矩阵,它和调用以下语句是一样的:

九、组合

之前的例子里面,我们总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。不过,我们可以利用 globalCompositeOperation 属性来改变这种状况。此外, clip属性允许我们隐藏不想看到的部分图形。

1. [globalCompositeOperation](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing%23globalCompositeOperation)

  • [globalCompositeOperation=type](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing%23globalCompositeOperation): 这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识12种遮盖方式的字符串。

2. 裁切路径

裁切路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分。如右图所示。红边五角星就是裁切路径,所有在路径以外的部分都不会在 canvas 上绘制出来。

image
  • [clip()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/clip): 将当前正在构建的路径转换为当前的裁剪路径。

十、动画

1. 动画的基本步骤

(1)清空 canvas
除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。
(2)保存 canvas 状态
如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
(3)绘制动画图形(animated shapes)
这一步才是重绘动画帧。
(4)恢复 canvas 状态
如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

2. 操控动画

在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常,我们仅仅在脚本执行结束后才能看见结果,比如说,在 for 循环里面做完成动画是不太可能的。

因此, 为了实现动画,我们需要一些可以定时执行重绘的方法。有两种方法可以实现这样的动画操控。首先可以通过 setInterval 和 setTimeout 方法来控制在设定的时间点上执行重绘。
(1)有安排的更新画布 Scheduled updates

  • [setInterval(function, delay)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/WindowTimers/setInterval):当设定好间隔时间后,function会定期执行。
  • [setTimeout(function, delay)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/WindowTimers/setTimeout):在设定好的时间之后执行函数
  • [requestAnimationFrame(callback)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame):告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。

十一、像素操作

1. ImageData 对象

根据行、列读取某像素点的R/G/B/A值的公式:

imageData.data[((行数-1)*imageData.width + (列数-1))*4 - 1 + 1/2/3/4];

2. 创建一个ImageData对象

  • var myImageData = ctx.createImageData(width, height): 上面代码创建了一个新的具体特定尺寸的ImageData对象。所有像素被预设为透明黑。
  • var myImageData = ctx.createImageData(anotherImageData): 你也可以创建一个被anotherImageData对象指定的相同像素的ImageData对象。这个新的对象像素全部被预设为透明黑。这个并非复制了图片数据。

3. 得到场景像素数据

var myImageData = ctx.getImageData(left, top, width, height);

这个方法会返回一个ImageData对象,它代表了画布区域的对象数据,此画布的四个角落分别表示为(left, top), (left + width, top), (left, top + height), 以及(left + width, top + height)四个点。这些坐标点被设定为画布坐标空间元素。

注:任何在画布以外的元素都会被返回成一个透明黑的ImageData对像。

3. 在场景中写入像素数据

ctx.putImageData(myImageData, dx, dy);: 你可以用putImageData()方法去对场景进行像素数据的写入。dx和dy参数表示你希望在场景内左上角绘制的像素数据所得到的设备坐标。

4. 保存图片

  • [canvas.toDataURL('image/png')](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL): 默认设定。创建一个PNG图片
  • [canvas.toDataURL('image/jpeg', quality)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL): 创建一个JPG图片。你可以有选择地提供从0到1的品质量,1表示最好品质,0基本不被辨析但有比较小的文件大小。
    当你从画布中生成了一个数据链接,例如,你可以将它用于任何[<image>](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/HTML/Element/image)元素,或者将它放在一个有download属性的超链接里用于保存到本地。
  • [canvas.toBlob(callback, type, encoderOptions)](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob): 这个创建了一个在画布中的代表图片的Blob对像。

十二、点击区域和无障碍访问

[<canvas>](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/HTML/Element/canvas) 标签只是一个位图,它并不提供任何已经绘制在上面的对象的信息。 canvas的内容不能像语义化的HTML一样暴露给一些协助工具。一般来说,你应该避免在交互型的网站或者App上使用canvas。接下来的内容能帮助你让canvas更加容易交互。

1. 内容兼容

<canvas> ... </canvas>标签里的内容被可以对一些不支持canvas的浏览器提供兼容。这对残疾用户设备也很有用(比如屏幕阅读器),这样它们就可以读取并解释DOM里的子节点。

2. ARIA 规则

Accessible Rich Internet Applications (ARIA) 定义了让Web内容和Web应用更容易被有身体缺陷的人获取的办法。你可以用ARIA属性来描述canvas元素的行为和存在目的。详情见ARIAARIA 技术
<canvas id="button" tabindex="0" role="button" aria-pressed="false" aria-label="Start game"></canvas>

3. 点击区域

判断鼠标坐标是否在canvas上一个特定区域里一直是个有待解决的问题。hit region API让你可以在canvas上定义一个区域,这让无障碍工具获取canvas上的交互内容成为可能。它能让你更容易地进行点击检测并把事件转发到DOM元素去。这个API有以下三个方法(都是实验性特性,请先在浏览器兼容表上确认再使用)。

  • [CanvasRenderingContext2D.addHitRegion()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/addHitRegion): 在canvas上添加一个点击区域。
  • [CanvasRenderingContext2D.removeHitRegion()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/removeHitRegion): 从canvas上移除指定id的点击区域。
  • [CanvasRenderingContext2D.clearHitRegions()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/clearHitRegions): 移除canvas上的所有点击区域。

你可以把一个点击区域添加到路径里并检测[MouseEvent.region](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent/region)属性来测试你的鼠标有没有点击这个区域,例:

<canvas id="canvas"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.arc(70, 80, 10, 0, 2 * Math.PI, false);
ctx.fill();
ctx.addHitRegion({id: "circle"});

canvas.addEventListener("mousemove", function(event){
  if(event.region) {
    alert("hit region: " + event.region);
  }
});
</script>

// addHitRegion()方法也可以带一个control选项来指定把事件转发到哪个元素上(canvas里的元素)。
ctx.addHitRegion({control: element});

4. 焦点圈

  • [CanvasRenderingContext2D.drawFocusIfNeeded()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawFocusIfNeeded): 如果给定的元素获得了焦点,这个方法会沿着在当前的路径画个焦点圈。
  • [CanvasRenderingContext2D.scrollPathIntoView()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/scrollPathIntoView): 把当前的路径或者一个给定的路径滚动到显示区域内。

十三、性能贴士

1. 在离屏canvas上预渲染相似的图形或重复的对象

如果你发现你的在每一帧里有好多复杂的画图运算,请考虑创建一个离屏canvas,将图像在这个画布上画一次(或者每当图像改变的时候画一次),然后在每帧上画出视线以外的这个画布。

2. 避免浮点数的坐标点,用整数取而代之

当你画一个没有整数坐标点的对象时会发生子像素渲染。浏览器为了达到抗锯齿的效果会做额外的运算。为了避免这种情况,请保证在你调用[drawImage()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage)函数时,用[Math.floor()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/floor)函数对所有的坐标点取整。

3. 不要在用drawImage时缩放图像

在离屏canvas中缓存图片的不同尺寸,而不要用[drawImage()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage)去缩放它们。

4. 使用多层画布去画一个复杂的场景

5. 用CSS设置大的背景图

如果像大多数游戏那样,你有一张静态的背景图,用一个静态的[<div>](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div)元素,结合[background](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/CSS/background) 特性,以及将它置于画布元素之后。这么做可以避免在每一帧在画布上绘制大图。

6. 用CSS transforms特性缩放画布

CSS transforms 特性由于调用GPU,因此更快捷。最好的情况是,不要将小画布放大,而是去将大画布缩小。例如Firefox系统,目标分辨率480 x 320 px。

var scaleX = canvas.width / window.innerWidth;
var scaleY = canvas.height / window.innerHeight;

var scaleToFit = Math.min(scaleX, scaleY);
var scaleToCover = Math.max(scaleX, scaleY);

stage.style.transformOrigin = '0 0'; //scale from top left
stage.style.transform = 'scale(' + scaleToFit + ')';

7. 关闭透明度

如果你的游戏使用画布而且不需要透明,当使用 [HTMLCanvasElement.getContext()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext "The HTMLCanvasElement.getContext() method returns a drawing context on the canvas, or null if the context identifier is not supported.") 创建一个绘图上下文时把 alpha 选项设置为 false 。这个选项可以帮助浏览器进行内部优化。
var ctx = canvas.getContext('2d', { alpha: false });

更多贴士

  • 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)
  • 避免不必要的画布状态改变
  • 渲染画布中的不同点,而非整个新状态
  • 尽可能避免 [shadowBlur](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/shadowBlur)特性
  • 尽可能避免text rendering
  • 使用不同的办法去清除画布([clearRect()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/clearRect) vs. [fillRect()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillRect) vs. 调整canvas大小)
  • 有动画,请使用[window.requestAnimationFrame()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame) 而非[window.setInterval()](https://link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/docs/Web/API/Window/setInterval)
  • 请谨慎使用大型物理库

例子

1. 绘制两个长方形,一个带透明度

<html>
 <head>
  <script type="application/javascript">
    function draw() {
      var canvas = document.getElementById("canvas");
      if (canvas.getContext) {
        var ctx = canvas.getContext("2d");

        ctx.fillStyle = "rgb(200,0,0)";
        ctx.fillRect (10, 10, 55, 50);

        ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
        ctx.fillRect (30, 30, 55, 50);
      }
    }
  </script>
 </head>
 <body onload="draw();">
   <canvas id="canvas" width="150" height="150"></canvas>
 </body>
</html>
image

2. 绘制一个实心三角形

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');

    ctx.beginPath();
    ctx.moveTo(75, 50);
    ctx.lineTo(100, 75);
    ctx.lineTo(100, 25);
    ctx.fill();
  }
}

[图片上传失败...(image-d3e7e0-1570584169259)]

3. 使用多个贝塞尔曲线

- 渲染对话气泡

function draw() {
 var canvas = document.getElementById('canvas');
 if (canvas.getContext) {
 var ctx = canvas.getContext('2d');

 // 二次贝塞尔曲线
 ctx.beginPath();
 ctx.moveTo(75,25);
 ctx.quadraticCurveTo(25,25,25,62.5);
 ctx.quadraticCurveTo(25,100,50,100);
 ctx.quadraticCurveTo(50,120,30,125);
 ctx.quadraticCurveTo(60,120,65,100);
 ctx.quadraticCurveTo(125,100,125,62.5);
 ctx.quadraticCurveTo(125,25,75,25);
 ctx.stroke();
  }
}
image

- 绘制心形

function draw() {
 var canvas = document.getElementById('canvas');
 if (canvas.getContext){
 var ctx = canvas.getContext('2d');

 //三次贝塞尔曲线
 ctx.beginPath();
 ctx.moveTo(75,40);
 ctx.bezierCurveTo(75,37,70,25,50,25);
 ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
 ctx.bezierCurveTo(20,80,40,102,75,120);
 ctx.bezierCurveTo(110,102,130,80,130,62.5);
 ctx.bezierCurveTo(130,62.5,130,25,100,25);
 ctx.bezierCurveTo(85,25,75,37,75,40);
 ctx.fill();
  }
}
image

4. 组合使用画游戏

function draw() {
 var canvas = document.getElementById('canvas');
 if (canvas.getContext){
 var ctx = canvas.getContext('2d');

 roundedRect(ctx,12,12,150,150,15);
 roundedRect(ctx,19,19,150,150,9);
 roundedRect(ctx,53,53,49,33,10);
 roundedRect(ctx,53,119,49,16,6);
 roundedRect(ctx,135,53,49,33,10);
 roundedRect(ctx,135,119,25,49,10);

 ctx.beginPath();
 ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,false);
 ctx.lineTo(31,37);
 ctx.fill();

 for(var i=0;i<8;i++){
 ctx.fillRect(51+i*16,35,4,4);
 }

 for(i=0;i<6;i++){
 ctx.fillRect(115,51+i*16,4,4);
 }

 for(i=0;i<8;i++){
 ctx.fillRect(51+i*16,99,4,4);
 }

 ctx.beginPath();
 ctx.moveTo(83,116);
 ctx.lineTo(83,102);
 ctx.bezierCurveTo(83,94,89,88,97,88);
 ctx.bezierCurveTo(105,88,111,94,111,102);
 ctx.lineTo(111,116);
 ctx.lineTo(106.333,111.333);
 ctx.lineTo(101.666,116);
 ctx.lineTo(97,111.333);
 ctx.lineTo(92.333,116);
 ctx.lineTo(87.666,111.333);
 ctx.lineTo(83,116);
 ctx.fill();

 ctx.fillStyle = "white";
 ctx.beginPath();
 ctx.moveTo(91,96);
 ctx.bezierCurveTo(88,96,87,99,87,101);
 ctx.bezierCurveTo(87,103,88,106,91,106);
 ctx.bezierCurveTo(94,106,95,103,95,101);
 ctx.bezierCurveTo(95,99,94,96,91,96);
 ctx.moveTo(103,96);
 ctx.bezierCurveTo(100,96,99,99,99,101);
 ctx.bezierCurveTo(99,103,100,106,103,106);
 ctx.bezierCurveTo(106,106,107,103,107,101);
 ctx.bezierCurveTo(107,99,106,96,103,96);
 ctx.fill();

 ctx.fillStyle = "black";
 ctx.beginPath();
 ctx.arc(101,102,2,0,Math.PI*2,true);
 ctx.fill();

 ctx.beginPath();
 ctx.arc(89,102,2,0,Math.PI*2,true);
 ctx.fill();
 }
}

// 封装的一个用于绘制圆角矩形的函数.

function roundedRect(ctx,x,y,width,height,radius){
  ctx.beginPath();
  ctx.moveTo(x,y+radius);
  ctx.lineTo(x,y+height-radius);
  ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
  ctx.lineTo(x+width-radius,y+height);
  ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
  ctx.lineTo(x+width,y+radius);
  ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
  ctx.lineTo(x+radius,y);
  ctx.quadraticCurveTo(x,y,x,y+radius);
  ctx.stroke();
}
image

5. Path2D 示例

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext){
    var ctx = canvas.getContext('2d');

    var rectangle = new Path2D();
    rectangle.rect(10, 10, 50, 50);

    var circle = new Path2D();
    circle.moveTo(125, 35);
    circle.arc(100, 35, 25, 0, 2 * Math.PI);

    ctx.stroke(rectangle);
    ctx.fill(circle);
  }
}
image

6. 文字阴影

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 2;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

  ctx.font = "20px Times New Roman";
  ctx.fillStyle = "Black";
  ctx.fillText("Sample String", 5, 30);
}
image

注意点

  1. Canvas 的默认大小为300像素×150像素(宽×高,像素的单位是px)。但是,可以使用HTML的高度和宽度属性来自定义Canvas 的尺寸。
  2. 如果你绘制出来的图像是扭曲的, 尝试用width和height属性为<canvas>明确规定宽高,而不是使用CSS。
  3. <canvas>元素可以像任何一个普通的图像一样(有margin,border,background等等属性)被设计。然而,这些样式不会影响在canvas中的实际图像。我们将会在一个专门的章节里看到这是如何解决的。当开始时没有为canvas规定样式规则,其将会完全透明。
  4. 与 <img> 元素不同,<canvas> 元素需要结束标签(</canvas>)。如果结束标签不存在,则文档的其余部分会被认为是替代内容,将不会显示出来。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容