diff options
Diffstat (limited to 'files/zh-cn/web/api/canvas_api/tutorial')
15 files changed, 4386 insertions, 0 deletions
diff --git a/files/zh-cn/web/api/canvas_api/tutorial/advanced_animations/index.html b/files/zh-cn/web/api/canvas_api/tutorial/advanced_animations/index.html new file mode 100644 index 0000000000..7547460968 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/advanced_animations/index.html @@ -0,0 +1,380 @@ +--- +title: 高级动画 +slug: Web/API/Canvas_API/Tutorial/Advanced_animations +tags: + - Canvas + - Graphics + - Tutorial +translation_of: Web/API/Canvas_API/Tutorial/Advanced_animations +--- +<div>{{CanvasSidebar}} {{ PreviousNext("Web/API/Canvas_API/Tutorial/Basic_animations", "Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas")}}</div> + +<div class="summary"> +<p>在上一章,我们制作了<a href="/zh-CN/docs/Web/Guide/HTML/Canvas_tutorial/Basic_animations">基本动画</a>以及逐步了解了让物件移动的方法。在这一部分,我们将会对运动有更深的了解并学会添加一些符合物理的运动以让我们的动画更加高级。</p> +</div> + +<h2 id="绘制小球">绘制小球</h2> + +<p>我们将会画一个小球用于动画学习,所以首先在画布上画一个球。下面的代码帮助我们建立画布。</p> + +<pre class="brush: html"><canvas id="canvas" width="600" height="300"></canvas> +</pre> + +<p>跟平常一样,我们需要先画一个 context(画布场景)。为了画出这个球,我们又会创建一个包含一些相关属性以及 <code>draw()</code> 函数的 <code>ball</code> 对象,来完成绘制。</p> + +<pre class="brush: js">var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); + +var ball = { + x: 100, + y: 100, + radius: 25, + color: 'blue', + draw: function() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.fill(); + } +}; + +ball.draw();</pre> + +<p>这里并没有什么特别的。小球实际上是一个简单的圆形,在{{domxref("CanvasRenderingContext2D.arc()", "arc()")}} 函数的帮助下画出。</p> + +<h2 id="添加速率">添加速率</h2> + +<p>现在我们有了一个小球,正准备添加一些基本动画,正如我们<a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_animations">上一章</a>所学的。又是这样,{{domxref("window.requestAnimationFrame()")}} 再一次帮助我们控制动画。小球依旧依靠添加速率矢量进行移动。在每一帧里面,我们依旧用{{domxref("CanvasRenderingContext2D.clearRect", "clear", "", 1)}} 清理掉之前帧里旧的圆形。</p> + +<pre class="brush: js; highlight:[8,9,24,25]">var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +var raf; + +var ball = { + x: 100, + y: 100, + vx: 5, + vy: 2, + radius: 25, + color: 'blue', + draw: function() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.fill(); + } +}; + +function draw() { + ctx.clearRect(0,0, canvas.width, canvas.height); + ball.draw(); + ball.x += ball.vx; + ball.y += ball.vy; + raf = window.requestAnimationFrame(draw); +} + +canvas.addEventListener('mouseover', function(e){ + raf = window.requestAnimationFrame(draw); +}); + +canvas.addEventListener('mouseout', function(e){ + window.cancelAnimationFrame(raf); +}); + +ball.draw(); +</pre> + +<h2 id="边界">边界 </h2> + +<p>若没有任何的碰撞检测,我们的小球很快就会超出画布。我们需要检查小球的 x 和 y 位置是否已经超出画布的尺寸以及是否需要将速度矢量反转。为了这么做,我们把下面的检查代码添加进 <code>draw</code> 函数:</p> + +<pre class="brush: js">if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) { + ball.vy = -ball.vy; +} +if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) { + ball.vx = -ball.vx; +}</pre> + +<h3 id="首个预览">首个预览</h3> + +<p>让我们看看现今它变得如何。移动你的鼠标到画布里开启动画。</p> + +<div class="hidden"> +<pre class="brush: html"><canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas></pre> + +<pre class="brush: js">var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +var raf; + +var ball = { + x: 100, + y: 100, + vx: 5, + vy: 2, + radius: 25, + color: 'blue', + draw: function() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.fill(); + } +}; + +function draw() { + ctx.clearRect(0,0, canvas.width, canvas.height); + ball.draw(); + ball.x += ball.vx; + ball.y += ball.vy; + + if (ball.y + ball.vy > canvas.height || + ball.y + ball.vy < 0) { + ball.vy = -ball.vy; + } + if (ball.x + ball.vx > canvas.width || + ball.x + ball.vx < 0) { + ball.vx = -ball.vx; + } + + raf = window.requestAnimationFrame(draw); +} + +canvas.addEventListener('mouseover', function(e) { + raf = window.requestAnimationFrame(draw); +}); + +canvas.addEventListener('mouseout', function(e) { + window.cancelAnimationFrame(raf); +}); + +ball.draw();</pre> +</div> + +<p>{{EmbedLiveSample("首个预览", "610", "310")}}</p> + +<h2 id="加速度">加速度</h2> + +<p>为了让动作更真实,你可以像这样处理速度,例如:</p> + +<pre class="brush: js">ball.vy *= .99; +ball.vy += .25;</pre> + +<p>这会逐帧减少垂直方向的速度,所以小球最终将只会在地板上弹跳。</p> + +<div class="hidden"> +<h6 id="Second_demo">Second demo</h6> + +<pre class="brush: html"><canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas></pre> + +<pre class="brush: js">var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +var raf; + +var ball = { + x: 100, + y: 100, + vx: 5, + vy: 2, + radius: 25, + color: 'blue', + draw: function() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.fill(); + } +}; + +function draw() { + ctx.clearRect(0,0, canvas.width, canvas.height); + ball.draw(); + ball.x += ball.vx; + ball.y += ball.vy; + ball.vy *= .99; + ball.vy += .25; + + if (ball.y + ball.vy > canvas.height || + ball.y + ball.vy < 0) { + ball.vy = -ball.vy; + } + if (ball.x + ball.vx > canvas.width || + ball.x + ball.vx < 0) { + ball.vx = -ball.vx; + } + + raf = window.requestAnimationFrame(draw); +} + +canvas.addEventListener('mouseover', function(e){ + raf = window.requestAnimationFrame(draw); +}); + +canvas.addEventListener('mouseout', function(e){ + window.cancelAnimationFrame(raf); +}); + +ball.draw();</pre> +</div> + +<p>{{EmbedLiveSample("Second_demo", "610", "310")}}</p> + +<h2 id="长尾效果">长尾效果</h2> + +<p>现在,我们使用的是 {{domxref("CanvasRenderingContext2D.clearRect", "clearRect")}} 函数帮我们清除前一帧动画。若用一个半透明的 {{domxref("CanvasRenderingContext2D.fillRect", "fillRect")}} 函数取代之,就可轻松制作长尾效果。</p> + +<pre class="brush: js">ctx.fillStyle = 'rgba(255,255,255,0.3)'; +ctx.fillRect(0,0,canvas.width,canvas.height);</pre> + +<div class="hidden"> +<h6 id="Third_demo">Third demo</h6> + +<pre class="brush: html"><canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas></pre> + +<pre class="brush: js">var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +var raf; + +var ball = { + x: 100, + y: 100, + vx: 5, + vy: 2, + radius: 25, + color: 'blue', + draw: function() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.fill(); + } +}; + +function draw() { + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.fillRect(0,0,canvas.width,canvas.height); + ball.draw(); + ball.x += ball.vx; + ball.y += ball.vy; + ball.vy *= .99; + ball.vy += .25; + + if (ball.y + ball.vy > canvas.height || + ball.y + ball.vy < 0) { + ball.vy = -ball.vy; + } + if (ball.x + ball.vx > canvas.width || + ball.x + ball.vx < 0) { + ball.vx = -ball.vx; + } + + raf = window.requestAnimationFrame(draw); +} + +canvas.addEventListener('mouseover', function(e){ + raf = window.requestAnimationFrame(draw); +}); + +canvas.addEventListener('mouseout', function(e){ + window.cancelAnimationFrame(raf); +}); + +ball.draw();</pre> +</div> + +<p>{{EmbedLiveSample("Third_demo", "610", "310")}}</p> + +<h2 id="添加鼠标控制">添加鼠标控制</h2> + +<p>为了更好地控制小球,我们可以用 <code style="font-style: normal;"><a href="/en-US/docs/Web/Reference/Events/mousemove">mousemove</a></code>事件让它跟随鼠标活动。下面例子中,<a href="/en-US/docs/Web/Events/click" style="font-family: Consolas, Monaco, 'Andale Mono', monospace;">click</a> 事件会释放小球然后让它重新跳起。</p> + +<div class="hidden"> +<pre class="brush: html"><canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas></pre> +</div> + +<pre class="brush: js">var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +var raf; +var running = false; + +var ball = { + x: 100, + y: 100, + vx: 5, + vy: 1, + radius: 25, + color: 'blue', + draw: function() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fillStyle = this.color; + ctx.fill(); + } +}; + +function clear() { + ctx.fillStyle = 'rgba(255,255,255,0.3)'; + ctx.fillRect(0,0,canvas.width,canvas.height); +} + +function draw() { + clear(); + ball.draw(); + ball.x += ball.vx; + ball.y += ball.vy; + + if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) { + ball.vy = -ball.vy; + } + if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) { + ball.vx = -ball.vx; + } + + raf = window.requestAnimationFrame(draw); +} + +canvas.addEventListener('mousemove', function(e){ + if (!running) { + clear(); + ball.x = e.offsetX; + ball.y = e.offsetY; + ball.draw(); + } +}); + +canvas.addEventListener('click',function(e){ + if (!running) { + raf = window.requestAnimationFrame(draw); + running = true; + } +}); + +canvas.addEventListener('mouseout', function(e){ + window.cancelAnimationFrame(raf); + running = false; +}); + +ball.draw(); +</pre> + +<p>用你的鼠标移动小球,点击可以释放。</p> + +<p>{{EmbedLiveSample("添加鼠标控制", "610", "310")}}</p> + +<h2 id="Breakout">Breakout</h2> + +<p>本短文仅仅解释了一小部分的创建高级动画的技巧。其实还有更多!试试添加一个球拍,一些砖块,然后将这个例子弄成一个 <a href="http://en.wikipedia.org/wiki/Breakout_%28video_game%29">Breakout</a>(译者注:打砖块游戏) 如何?查看我们的<a href="/en-US/docs/Games">游戏开发</a>区去查阅更多相关文章。</p> + +<h2 id="参考">参考</h2> + +<ul> + <li>{{domxref("window.requestAnimationFrame()")}}</li> + <li><a href="/en-US/docs/Games/Techniques/Efficient_animation_for_web_games">网页动画高效开发</a></li> +</ul> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Basic_animations", "Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/applying_styles_and_colors/index.html b/files/zh-cn/web/api/canvas_api/tutorial/applying_styles_and_colors/index.html new file mode 100644 index 0000000000..f91fa50a87 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/applying_styles_and_colors/index.html @@ -0,0 +1,684 @@ +--- +title: 使用样式和颜色 +slug: Web/API/Canvas_API/Tutorial/Applying_styles_and_colors +tags: + - Canvas +translation_of: Web/API/Canvas_API/Tutorial/Applying_styles_and_colors +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Drawing_shapes", "Web/API/Canvas_API/Tutorial/Drawing_text")}}</div> + +<div class="summary">在<a href="/cn/Canvas_tutorial/Drawing_shapes" title="cn/Canvas tutorial/Drawing shapes">绘制图形</a>的章节里,我只用到默认的线条和填充样式。而在这一章里,我们将会探讨 canvas 全部的可选项,来绘制出更加吸引人的内容。</div> + +<h2 id="Colors" name="Colors">色彩 Colors</h2> + +<p>到目前为止,我们只看到过绘制内容的方法。如果我们想要给图形上色,有两个重要的属性可以做到:<code>fillStyle</code> <span>和 </span><code>strokeStyle。</code></p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.fillStyle", "fillStyle = color")}}</dt> + <dd>设置图形的填充颜色。</dd> + <dt>{{domxref("CanvasRenderingContext2D.strokeStyle", "strokeStyle = color")}}</dt> + <dd>设置图形轮廓的颜色。</dd> +</dl> + +<p><code>color</code> 可以是表示 CSS 颜色值的字符串,渐变对象或者图案对象。我们迟些再回头探讨渐变和图案对象。默认情况下,线条和填充颜色都是黑色(CSS 颜色值 <code>#000000</code>)。</p> + +<div class="note"> +<p><strong>注意:</strong> 一旦您设置了 <code>strokeStyle</code> 或者 <code>fillStyle</code> 的值,那么这个新值就会成为新绘制的图形的默认值。如果你要给每个图形上不同的颜色,你需要重新设置 <code>fillStyle</code> 或 <code>strokeStyle</code> 的值。</p> +</div> + +<p>您输入的应该是符合 <a class="external" href="https://www.w3.org/TR/css-color-3/">CSS3 颜色值标准</a> 的有效字符串。下面的例子都表示同一种颜色。</p> + +<pre class="brush: js notranslate">// 这些 fillStyle 的值均为 '橙色' +ctx.fillStyle = "orange"; +ctx.fillStyle = "#FFA500"; +ctx.fillStyle = "rgb(255,165,0)"; +ctx.fillStyle = "rgba(255,165,0,1)"; +</pre> + +<h3 id="A_fillStyle_example" name="A_fillStyle_example"><code>fillStyle</code> 示例</h3> + +<p>在本示例里,我会再度用两层 <code>for</code> 循环来绘制方格阵列,每个方格不同的颜色。结果如右图,但实现所用的代码却没那么绚丽。我用了两个变量 i 和 j 来为每一个方格产生唯一的 RGB 色彩值,其中仅修改红色和绿色通道的值,而保持蓝色通道的值不变。你可以通过修改这些颜色通道的值来产生各种各样的色板。通过增加渐变的频率,你还可以绘制出类似 Photoshop 里面的那样的调色板。</p> + +<pre class="brush: js;highlight[5,6] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + for (var i=0;i<6;i++){ + for (var j=0;j<6;j++){ + ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' + + Math.floor(255-42.5*j) + ',0)'; + ctx.fillRect(j*25,i*25,25,25); + } + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>结果如下:</p> + +<p>{{EmbedLiveSample("A_fillStyle_example", 160, 160, "https://mdn.mozillademos.org/files/5417/Canvas_fillstyle.png")}}</p> + +<h3 id="A_strokeStyle_example" name="A_strokeStyle_example"><code>strokeStyle</code> 示例</h3> + +<p>这个示例与上面的有点类似,但这次用到的是 <code>strokeStyle</code> 属性,画的不是方格,而是用 <code>arc</code> 方法来画圆。</p> + +<pre class="brush: js notranslate"> function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + for (var i=0;i<6;i++){ + for (var j=0;j<6;j++){ + ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' + + Math.floor(255-42.5*j) + ')'; + ctx.beginPath(); + ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true); + ctx.stroke(); + } + } + } +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>结果如下:</p> + +<p>{{EmbedLiveSample("A_strokeStyle_example", "180", "180", "https://mdn.mozillademos.org/files/253/Canvas_strokestyle.png")}}</p> + +<h2 id="Transparency" name="Transparency">透明度 <span>Transparency</span></h2> + +<p>除了可以绘制实色图形,我们还可以用 canvas 来绘制半透明的图形。通过设置 <code>globalAlpha</code> 属性或者使用一个半透明颜色作为轮廓或填充的样式。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.globalAlpha", "globalAlpha = transparencyValue")}}</dt> + <dd>这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。</dd> +</dl> + +<p><code>globalAlpha</code> 属性在需要绘制大量拥有相同透明度的图形时候相当高效。不过,我认为下面的方法可操作性更强一点。</p> + +<p>因为 <code>strokeStyle</code> 和 <code>fillStyle</code> 属性接受符合 CSS 3 规范的颜色值,那我们可以用下面的写法来设置具有透明度的颜色。</p> + +<pre class="brush: js notranslate">// <span>指定</span><span>透明颜色,用于描边</span><span>和</span><span>填充样式 +</span>ctx.strokeStyle = "rgba(255,0,0,0.5)"; +ctx.fillStyle = "rgba(255,0,0,0.5)"; +</pre> + +<p><code>rgba() </code>方法与 <code>rgb() </code>方法类似,就多了一个用于设置色彩透明度的参数。它的有效范围是从 0.0(完全透明)到 1.0(完全不透明)。</p> + +<h3 id="A_globalAlpha_example" name="A_globalAlpha_example"><code>globalAlpha</code> 示例</h3> + +<p>在这个例子里,将用四色格作为背景,设置 <code>globalAlpha</code> 为 <code>0.2</code> 后,在上面画一系列半径递增的半透明圆。最终结果是一个径向渐变效果。圆叠加得越更多,原先所画的圆的透明度会越低。通过增加循环次数,画更多的圆,从中心到边缘部分,背景图会呈现逐渐消失的效果。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + // 画背景 + ctx.fillStyle = '#FD0'; + ctx.fillRect(0,0,75,75); + ctx.fillStyle = '#6C0'; + ctx.fillRect(75,0,75,75); + ctx.fillStyle = '#09F'; + ctx.fillRect(0,75,75,75); + ctx.fillStyle = '#F30'; + ctx.fillRect(75,75,75,75); + ctx.fillStyle = '#FFF'; + + // 设置透明度值 + ctx.globalAlpha = 0.2; + + // 画半透明圆 + for (var i=0;i<7;i++){ + ctx.beginPath(); + ctx.arc(75,75,10+10*i,0,Math.PI*2,true); + ctx.fill(); + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_globalAlpha_example", "180", "180", "https://mdn.mozillademos.org/files/232/Canvas_globalalpha.png")}}</p> + +<h3 id="An_example_using_rgba" name="An_example_using_rgba"><code>rgba()</code> 示例</h3> + +<p>第二个例子和上面那个类似,不过不是画圆,而是画矩形。这里还可以看出,rgba() 可以分别设置轮廓和填充样式,因而具有更好的可操作性和使用<span class="hps">灵活性</span>。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + // 画背景 + ctx.fillStyle = 'rgb(255,221,0)'; + ctx.fillRect(0,0,150,37.5); + ctx.fillStyle = 'rgb(102,204,0)'; + ctx.fillRect(0,37.5,150,37.5); + ctx.fillStyle = 'rgb(0,153,255)'; + ctx.fillRect(0,75,150,37.5); + ctx.fillStyle = 'rgb(255,51,0)'; + ctx.fillRect(0,112.5,150,37.5); + + // 画半透明矩形 + for (var i=0;i<10;i++){ + ctx.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')'; + for (var j=0;j<4;j++){ + ctx.fillRect(5+i*14,5+j*37.5,14,27.5) + } + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("An_example_using_rgba", "180", "180", "https://mdn.mozillademos.org/files/246/Canvas_rgba.png")}}</p> + +<h2 id="Line_styles" name="Line_styles">线型 Line styles</h2> + +<p>可以通过一系列属性来设置线的样式。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.lineWidth", "lineWidth = value")}}</dt> + <dd>设置线条宽度。</dd> + <dt>{{domxref("CanvasRenderingContext2D.lineCap", "lineCap = type")}}</dt> + <dd>设置线条末端样式。</dd> + <dt>{{domxref("CanvasRenderingContext2D.lineJoin", "lineJoin = type")}}</dt> + <dd><span>设定线条与线条间接合处的样式</span>。</dd> + <dt>{{domxref("CanvasRenderingContext2D.miterLimit", "miterLimit = value")}}</dt> + <dd><span>限制当两条线相交时交接处最大长度</span><span>;</span><span>所谓交接处长度</span><span>(</span><span>斜接长度</span><span>)</span><span>是指线条交接处内角顶点到外角顶点的长度</span><span>。</span></dd> +</dl> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.getLineDash", "getLineDash()")}}</dt> + <dd>返回一个包含当前虚线样式,长度为非负<span>偶数的</span>数组。</dd> + <dt>{{domxref("CanvasRenderingContext2D.setLineDash", "setLineDash(segments)")}}</dt> + <dd>设置当前虚线样式。</dd> + <dt>{{domxref("CanvasRenderingContext2D.lineDashOffset", "lineDashOffset = value")}}</dt> + <dd>设置虚线样式的起始偏移量。</dd> +</dl> + +<p>通过以下的样例可能会更加容易理解。</p> + +<h4 id="A_lineWidth_example" name="A_lineWidth_example"><code>lineWidth</code> 属性的例子</h4> + +<p>这个属性设置当前绘线的粗细。属性值必须为正数。默认值是1.0。</p> + +<p>线宽是指给定路径的中心到两边的粗细。换句话说就是在路径的两边各绘制线宽的一半。因为画布的坐标并不和像素直接对应,当需要获得精确的水平或垂直线的时候要特别注意。</p> + +<p>在下面的例子中,用递增的宽度绘制了10条直线。最左边的线宽1.0单位。并且,最左边的以及所有宽度为奇数的线并不能精确呈现,这就是因为路径的定位问题。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + for (var i = 0; i < 10; i++){ + ctx.lineWidth = 1+i; + ctx.beginPath(); + ctx.moveTo(5+i*14,5); + ctx.lineTo(5+i*14,140); + ctx.stroke(); + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_lineWidth_example", "180", "180", "https://mdn.mozillademos.org/files/239/Canvas_linewidth.png")}}</p> + +<p>想要获得精确的线条,必须对线条是如何描绘出来的有所理解。见下图,用网格来代表 canvas 的坐标格,每一格对应屏幕上一个像素点。在第一个图中,填充了 (2,1) 至 (5,5) 的矩形,整个区域的边界刚好落在像素边缘上,这样就可以得到的矩形有着清晰的边缘。</p> + +<p><img alt="" class="internal" src="/@api/deki/files/601/=Canvas-grid.png"></p> + +<p>如果你想要绘制一条从 (3,1) 到 (3,5),宽度是 1.0 的线条,你会得到像第二幅图一样的结果。实际填充区域(深蓝色部分)仅仅延伸至路径两旁各一半像素。而这半个像素又会以近似的方式进行渲染,这意味着那些像素只是部分着色,结果就是以实际笔触颜色一半色调的颜色来填充整个区域(浅蓝和深蓝的部分)。这就是上例中为何宽度为 1.0 的线并不准确的原因。</p> + +<p>要解决这个问题,你必须对路径施以更加精确的控制。已知粗 1.0 的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5,1) 到 (3.5,5) 的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0 的线条。</p> + +<div class="note"> +<p><strong>注意:</strong>在这个竖线的例子中,其Y坐标刚好落在网格线上,否则端点上同样会出现半渲染的像素点(但还要注意,这种行为的表现取决于当前的lineCap风格,它默认为butt;您可能希望通过将lineCap样式设置为square正方形,来得到与奇数宽度线的半像素坐标相一致的笔画,这样,端点轮廓的外边框将被自动扩展以完全覆盖整个像素格)。</p> + +<p>还请注意,只有路径的起点和终点受此影响:如果一个路径是通过closePath()来封闭的,它是没有起点和终点的;相反的情况下,路径上的所有端点都与上一个点相连,下一段路径使用当前的lineJoin设置(默认为miter),如果相连路径是水平和/或垂直的话,会导致相连路径的外轮廓根据相交点自动延伸,因此渲染出的路径轮廓会覆盖整个像素格。接下来的两个小节将展示这些额外的行样式。</p> +</div> + +<p>对于那些宽度为偶数的线条,每一边的像素数都是整数,那么你想要其路径是落在像素点之间 (如那从 (3,1) 到 (3,5)) 而不是在像素点的中间。同样,注意到那个例子的垂直线条,其 Y 坐标刚好落在网格线上,如果不是的话,端点上同样会出现半渲染的像素点。</p> + +<p style="text-align: justify;">虽然开始处理可缩放的 2D 图形时会有点小痛苦,但是及早注意到像素网格与路径位置之间的关系,可以确保图形在经过缩放或者其它任何变形后都可以保持看上去蛮好:线宽为 1.0 的垂线在放大 2 倍后,会变成清晰的线宽为 2.0,并且出现在它应该出现的位置上。</p> + +<h4 id="A_lineCap_example" name="A_lineCap_example"><code>lineCap</code> 属性的例子</h4> + +<p><img alt="" class="internal" src="/@api/deki/files/88/=Canvas_linecap.png" style="float: right;">属性 <code>lineCap</code> 的值决定了线段端点显示的样子。它可以为下面的三种的其中之一:<code>butt</code>,<code>round</code> 和 <code>square</code>。默认是 <code>butt。</code></p> + +<p>在这个例子里面,我绘制了三条直线,分别赋予不同的 <code>lineCap</code> 值。还有两条辅助线,为了可以看得更清楚它们之间的区别,三条线的起点终点都落在辅助线上。</p> + +<p>最左边的线用了默认的 <code>butt</code> 。可以注意到它是与辅助线齐平的。中间的是 <code>round</code> 的效果,端点处加上了半径为一半线宽的半圆。右边的是 <code>square</code> 的效果,端点处加上了等宽且高度为一半线宽的方块。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + var lineCap = ['butt','round','square']; + + // 创建路径 + ctx.strokeStyle = '#09f'; + ctx.beginPath(); + ctx.moveTo(10,10); + ctx.lineTo(140,10); + ctx.moveTo(10,140); + ctx.lineTo(140,140); + ctx.stroke(); + + // 画线条 + ctx.strokeStyle = 'black'; + for (var i=0;i<lineCap.length;i++){ + ctx.lineWidth = 15; + ctx.lineCap = lineCap[i]; + ctx.beginPath(); + ctx.moveTo(25+i*50,10); + ctx.lineTo(25+i*50,140); + ctx.stroke(); + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_lineCap_example", "180", "180", "https://mdn.mozillademos.org/files/236/Canvas_linecap.png")}}</p> + +<h4 id="A_lineJoin_example" name="A_lineJoin_example"><code>lineJoin</code> 属性的例子</h4> + +<p><img alt="" class="internal" src="/@api/deki/files/89/=Canvas_linejoin.png" style="float: right;"><code>lineJoin</code> 的属性值决定了图形中两线段连接处所显示的样子。它可以是这三种之一:<code>round</code>, <code>bevel</code> 和 <code>miter。</code>默认是 <code>miter</code><code>。</code></p> + +<p>这里我同样用三条折线来做例子,分别设置不同的 <code>lineJoin</code> 值。最上面一条是 <code>round</code> 的效果,边角处被磨圆了,圆的半径等于线宽。中间和最下面一条分别是 bevel 和 miter 的效果。当值是 <code>miter </code>的时候,线段会在连接处外侧延伸直至交于一点,延伸效果受到下面将要介绍的 <code>miterLimit</code> 属性的制约。</p> + +<pre class="brush: js;highlight[6] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + var lineJoin = ['round', 'bevel', 'miter']; + ctx.lineWidth = 10; + for (var i = 0; i < lineJoin.length; i++) { + ctx.lineJoin = lineJoin[i]; + ctx.beginPath(); + ctx.moveTo(-5, 5 + i * 40); + ctx.lineTo(35, 45 + i * 40); + ctx.lineTo(75, 5 + i * 40); + ctx.lineTo(115, 45 + i * 40); + ctx.lineTo(155, 5 + i * 40); + ctx.stroke(); + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_lineJoin_example", "180", "180", "https://mdn.mozillademos.org/files/237/Canvas_linejoin.png")}}</p> + +<h4 id="A_demo_of_the_miterLimit_property" name="A_demo_of_the_miterLimit_property"><code>miterLimit</code> 属性的演示例子</h4> + +<p>就如上一个例子所见的应用 <code>miter</code> 的效果,线段的外侧边缘会被延伸交汇于一点上。线段之间夹角比较大时,交点不会太远,但随着夹角变小,交点距离会呈指数级增大。</p> + +<p><code>miterLimit</code> 属性就是用来设定外延交点与连接点的最大距离,如果交点距离大于此值,连接效果会变成了 bevel。注意,最大斜接长度(即交点距离)是当前坐标系测量线宽与此<code>miterLimit</code>属性值(HTML {{HTMLElement("canvas")}}默认为10.0)的乘积,所以<code>miterLimit</code>可以单独设置,不受显示比例改变或任何仿射变换的影响:它只影响线条边缘的有效绘制形状。</p> + +<p>更准确的说,斜接限定值(<code>miterLimit</code>)是延伸长度(在HTML Canvas中,这个值是线段外连接点与路径中指定的点之间的距离)与一半线宽的最大允许比值。它也可以被等效定义为线条内外连接点距离(<code>miterLength</code>)与线宽(<code>lineWidth</code>)的最大允许比值(因为路径点是内外连接点的中点)。这等同于相交线段最小内夹角(<em>θ</em> )的一半的余割值,小于此角度的斜接将不会被渲染,而仅渲染斜边连接:</p> + +<ul> + <li><code>miterLimit</code> = <strong>max</strong> <code>miterLength</code> / <code>lineWidth</code> = 1 / <strong>sin</strong> ( <strong>min</strong> <em>θ</em> / 2 )</li> + <li>斜接限定值默认为10.0,这将会去除所有小于大约11度的斜接。</li> + <li>斜接限定值为√2 ≈ 1.4142136 (四舍五入)时,将去除所有锐角的斜接,仅保留钝角或直角。</li> + <li>1.0是合法的斜接限定值,但这会去除所有斜接。</li> + <li>小于1.0的值不是合法的斜接限定值。</li> +</ul> + +<p>在下面的小示例中,您可以动态的设置<code>miterLimit</code>的值并查看它对画布中图形的影响。蓝色线条指出了锯齿图案中每个线条的起点与终点(同时也是不同线段之间的连接点)。</p> + +<p>在此示例中,当您设定<code>miterLimit</code>的值小于4.2时,图形可见部分的边角不会延伸相交,而是在蓝色线条边呈现斜边连接效果;当<code>miterLimit</code>的值大于10.0时,此例中大部分的边角都会在远离蓝线的位置相交,且从左至右,距离随着夹角的增大而减小;而介于上述值之间的值所呈现的效果,也介于两者之间。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + // 清空画布 + ctx.clearRect(0, 0, 150, 150); + + // 绘制参考线 + ctx.strokeStyle = '#09f'; + ctx.lineWidth = 2; + ctx.strokeRect(-5, 50, 160, 50); + + // 设置线条样式 + ctx.strokeStyle = '#000'; + ctx.lineWidth = 10; + + // 检查输入 + if (document.getElementById('miterLimit').value.match(/\d+(\.\d+)?/)) { + ctx.miterLimit = parseFloat(document.getElementById('miterLimit').value); + } else { + alert('Value must be a positive number'); + } + + // 绘制线条 + ctx.beginPath(); + ctx.moveTo(0, 100); + for (i = 0; i < 24 ; i++) { + var dy = i % 2 == 0 ? 25 : -25; + ctx.lineTo(Math.pow(i, 1.5) * 2, 75 + dy); + } + ctx.stroke(); + return false; +}</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><table> + <tr> + <td><canvas id="canvas" width="150" height="150"></canvas></td> + <td>在输入框中输入<code>miterLimit</code>的值,并点击重绘按钮查看效果。<br><br> + <form onsubmit="return draw();"> + <label>Miter limit</label> + <input type="number" size="3" id="miterLimit"/> + <input type="submit" value="重绘"/> + </form> + </td> + </tr> +</table></pre> + +<pre class="brush: js notranslate">document.getElementById('miterLimit').value = document.getElementById('canvas').getContext('2d').miterLimit; +draw();</pre> +</div> + +<p> {{EmbedLiveSample("A_demo_of_the_miterLimit_property", "500", "240", "https://mdn.mozillademos.org/files/240/Canvas_miterlimit.png")}}</p> + +<h3 id="Using_line_dashes" name="Using_line_dashes">使用虚线</h3> + +<p>用 <code>setLineDash</code> 方法和 <code>lineDashOffset</code> 属性来制定虚线样式. <code>setLineDash</code> 方法接受一个数组,来指定线段与间隙的交替;<code>lineDashOffset </code>属性设置起始偏移量.</p> + +<p>在这个例子中,我们要创建一个蚂蚁线的效果。它往往应用在计算机图形程序选区工具动效中。它可以帮助用户通过动画的边界来区分图像背景选区边框。在本教程的后面部分,你可以学习如何实现这一点和其他<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_animations">基本的动画</a>。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="110" height="110"></canvas></pre> +</div> + +<pre class="brush: js notranslate"><code>var ctx = document.getElementById('canvas').getContext('2d'); +var offset = 0;</code> + +<code>function draw() { + ctx.clearRect(0,0, canvas.width, canvas.height); + ctx.setLineDash([4, 2]); + ctx.lineDashOffset = -offset; + ctx.strokeRect(10,10, 100, 100); +}</code> + +<code>function march() { + offset++; + if (offset > 16) { + offset = 0; + } + draw(); + setTimeout(march, 20); +}</code> + +<code>march();</code> +</pre> + +<p>{{EmbedLiveSample("Using_line_dashes", "120", "120", "https://mdn.mozillademos.org/files/9853/marching-ants.png")}}</p> + +<h2 id="Gradients" name="Gradients">渐变 Gradients</h2> + +<p>就好像一般的绘图软件一样,我们可以用线性或者径向的渐变来填充或描边。我们用下面的方法新建一个 <code>canvasGradient</code> 对象,并且赋给图形的 <code>fillStyle</code> 或 <code>strokeStyle</code> 属性。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.createLinearGradient", "createLinearGradient(x1, y1, x2, y2)")}}</dt> + <dd><font face="Source Code Pro Semibold">createLinearGradient</font> 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。</dd> + <dt>{{domxref("CanvasRenderingContext2D.createRadialGradient", "createRadialGradient(x1, y1, r1, x2, y2, r2)")}}</dt> + <dd><font face="Source Code Pro Semibold">createRadialGradient</font> 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。</dd> +</dl> + +<pre class="brush: js notranslate">var lineargradient = ctx.createLinearGradient(0,0,150,150); +var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100); +</pre> + +<p>创建出 <code>canvasGradient</code> 对象后,我们就可以用 <code>addColorStop</code> 方法给它上色了。</p> + +<dl> + <dt>{{domxref("CanvasGradient.addColorStop", "gradient.addColorStop(position, color)")}}</dt> + <dd><font face="Source Code Pro Semibold">addColorStop</font> 方法接受 2 个参数,<code><font face="Source Code Pro Semibold">position</font></code> 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。<code><font face="Source Code Pro Semibold">color</font></code> 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。</dd> +</dl> + +<p>你可以根据需要添加任意多个色标(color stops)。下面是最简单的线性黑白渐变的例子。</p> + +<pre class="brush: js notranslate">var lineargradient = ctx.createLinearGradient(0,0,150,150); +lineargradient.addColorStop(0,'white'); +lineargradient.addColorStop(1,'black'); +</pre> + +<h4 id="A_createLinearGradient_example" name="A_createLinearGradient_example"><code>createLinearGradient</code> 的例子<img alt="" class="internal" src="/@api/deki/files/87/=Canvas_lineargradient.png" style="float: right;"></h4> + +<p>本例中,我弄了两种不同的渐变。第一种是背景色渐变,你会发现,我给同一位置设置了两种颜色,你也可以用这来实现突变的效果,就像这里从白色到绿色的突变。一般情况下,色标的定义是无所谓顺序的,但是色标位置重复时,顺序就变得非常重要了。所以,保持色标定义顺序和它理想的顺序一致,结果应该没什么大问题。</p> + +<p>第二种渐变,我并不是从 0.0 位置开始定义色标,因为那并不是那么严格的。在 0.5 处设一黑色色标,渐变会默认认为从起点到色标之间都是黑色。</p> + +<p>你会发现,<code>strokeStyle</code> 和 <code>fillStyle</code> 属性都可以接受 <code>canvasGradient</code> 对象。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + // Create gradients + var lingrad = ctx.createLinearGradient(0,0,0,150); + lingrad.addColorStop(0, '#00ABEB'); + lingrad.addColorStop(0.5, '#fff'); + lingrad.addColorStop(0.5, '#26C000'); + lingrad.addColorStop(1, '#fff'); + + var lingrad2 = ctx.createLinearGradient(0,50,0,95); + lingrad2.addColorStop(0.5, '#000'); + lingrad2.addColorStop(1, 'rgba(0,0,0,0)'); + + // assign gradients to fill and stroke styles + ctx.fillStyle = lingrad; + ctx.strokeStyle = lingrad2; + + // draw shapes + ctx.fillRect(10,10,130,130); + ctx.strokeRect(50,50,50,50); + +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_createLinearGradient_example", "180", "180", "https://mdn.mozillademos.org/files/235/Canvas_lineargradient.png")}}</p> + +<h3 id="A_createRadialGradient_example" name="A_createRadialGradient_example"><code>createRadialGradient</code> 的例子</h3> + +<p>这个例子,我定义了 4 个不同的径向渐变。由于可以控制渐变的起始与结束点,所以我们可以实现一些比(如在 Photoshop 中所见的)经典的径向渐变更为复杂的效果。(经典的径向渐变是只有一个中心点,简单地由中心点向外围的圆形扩张)</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + // 创建渐变 + var radgrad = ctx.createRadialGradient(45,45,10,52,50,30); + radgrad.addColorStop(0, '#A7D30C'); + radgrad.addColorStop(0.9, '#019F62'); + radgrad.addColorStop(1, 'rgba(1,159,98,0)'); + + var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50); + radgrad2.addColorStop(0, '#FF5F98'); + radgrad2.addColorStop(0.75, '#FF0188'); + radgrad2.addColorStop(1, 'rgba(255,1,136,0)'); + + var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40); + radgrad3.addColorStop(0, '#00C9FF'); + radgrad3.addColorStop(0.8, '#00B5E2'); + radgrad3.addColorStop(1, 'rgba(0,201,255,0)'); + + var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90); + radgrad4.addColorStop(0, '#F4F201'); + radgrad4.addColorStop(0.8, '#E4C700'); + radgrad4.addColorStop(1, 'rgba(228,199,0,0)'); + + // 画图形 + ctx.fillStyle = radgrad4; + ctx.fillRect(0,0,150,150); + ctx.fillStyle = radgrad3; + ctx.fillRect(0,0,150,150); + ctx.fillStyle = radgrad2; + ctx.fillRect(0,0,150,150); + ctx.fillStyle = radgrad; + ctx.fillRect(0,0,150,150); +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>这里,我让起点稍微偏离终点,这样可以达到一种球状 3D 效果。但最好不要让里圆与外圆部分交叠,那样会产生什么效果就真是不得而知了。</p> + +<p>4 个径向渐变效果的最后一个色标都是透明色。如果想要两色标直接的过渡柔和一些,只要两个颜色值一致就可以了。代码里面看不出来,是因为我用了两种不同的颜色表示方法,但其实是相同的<span style="font-family: monospace;">,</span><code><font face="Source Code Pro Semibold">#019F62 = rgba(1,159,98,1)。</font></code></p> + +<p>{{EmbedLiveSample("A_createRadialGradient_example", "180", "180", "https://mdn.mozillademos.org/files/244/Canvas_radialgradient.png")}}</p> + +<h2 id="Patterns" name="Patterns">图案样式 Patterns</h2> + +<p>上一节的一个例子里面,我用了循环来实现图案的效果。其实,有一个更加简单的方法:<code>createPattern。</code></p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.createPattern", "createPattern(image, type)")}}</dt> + <dd>该方法接受两个参数。Image 可以是一个 <code><font face="Source Code Pro Semibold">Image</font></code> 对象的引用,或者另一个 canvas 对象。<code>Type</code> 必须是下面的字符串值之一:<code><font face="Source Code Pro Semibold">repeat</font></code>,<code><font face="Source Code Pro Semibold">repeat-x</font></code>,<code><font face="Source Code Pro Semibold">repeat-y</font></code> 和 <code><font face="Source Code Pro Semibold">no-repeat</font></code>。</dd> +</dl> + +<div class="note"><strong>注意:</strong> 用 canvas 对象作为 <code>Image</code> 参数在 Firefox 1.5 (Gecko 1.8) 中是无效的。</div> + +<p>图案的应用跟渐变很类似的,创建出一个 pattern 之后,赋给 <code>fillStyle</code> 或 <code>strokeStyle</code> 属性即可。</p> + +<pre class="brush: js notranslate">var img = new Image(); +img.src = 'someimage.png'; +var ptrn = ctx.createPattern(img,'repeat'); +</pre> + +<div class="note"> +<p><strong>注意:</strong>与 drawImage 有点不同,你需要确认 image 对象已经装载完毕,否则图案可能效果不对的。</p> +</div> + +<h3 id="A_createPattern_example" name="A_createPattern_example"><code>createPattern</code> 的例子</h3> + +<p>在最后的例子中,我创建一个图案然后赋给了 <code>fillStyle</code> 属性。唯一要注意的是,使用 Image 对象的 <code>onload</code> handler 来确保设置图案之前图像已经装载完毕。</p> + +<pre class="brush: js;highlight[10] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + // 创建新 image 对象,用作图案 + var img = new Image(); + img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png'; + img.onload = function() { + + // 创建图案 + var ptrn = ctx.createPattern(img, 'repeat'); + ctx.fillStyle = ptrn; + ctx.fillRect(0, 0, 150, 150); + + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> + +<p>The result looks like this:</p> +</div> + +<p>{{EmbedLiveSample("A_createPattern_example", "180", "180", "https://mdn.mozillademos.org/files/222/Canvas_createpattern.png")}}</p> + +<h2 id="阴影_Shadows">阴影 Shadows</h2> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.shadowOffsetX", "shadowOffsetX = float")}}</dt> + <dd> + <p><code><font face="Source Code Pro Semibold">shadowOffsetX</font></code> <span style="font-family: monospace;">和 </span><code><font face="Source Code Pro Semibold">shadowOffsetY </font></code>用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 <code><font face="Source Code Pro Semibold">0</font></code>。</p> + </dd> + <dt>{{domxref("CanvasRenderingContext2D.shadowOffsetY", "shadowOffsetY = float")}}</dt> + <dd><font face="Source Code Pro Semibold">shadowOffsetX</font> <span style="font-family: monospace;">和 </span><code><font face="Source Code Pro Semibold">shadowOffsetY </font></code>用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 <code><font face="Source Code Pro Semibold">0</font></code>。</dd> + <dt>{{domxref("CanvasRenderingContext2D.shadowBlur", "shadowBlur = float")}}</dt> + <dd><font face="Source Code Pro Semibold">shadowBlur</font> 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 <code><font face="Source Code Pro Semibold">0</font></code>。</dd> + <dt>{{domxref("CanvasRenderingContext2D.shadowColor", "shadowColor = color")}}</dt> + <dd><font face="Source Code Pro Semibold">shadowColor</font> 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。</dd> +</dl> + +<h3 id="A_shadowed_text_example" name="A_shadowed_text_example">文字阴影的例子</h3> + +<p>这个例子绘制了带阴影效果的文字。</p> + +<pre class="brush: js notranslate">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); +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="80"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_shadowed_text_example", "180", "100", "https://mdn.mozillademos.org/files/2505/shadowed-string.png")}}</p> + +<p>我们可以通过下一章来了解文字属性和fillText方法相关的内容。</p> + +<h2 id="Canvas_fill_rules" name="Canvas_fill_rules">Canvas 填充规则</h2> + +<p>当我们用到 <code>fill</code>(或者 {{domxref("CanvasRenderingContext2D.clip", "clip")}}和{{domxref("CanvasRenderingContext2D.isPointInPath", "isPointinPath")}} )你可以选择一个填充规则,该填充规则根据某处在路径的外面或者里面来决定该处是否被填充,这对于自己与自己路径相交或者路径被嵌套的时候是有用的。</p> + +<p>两个可能的值:</p> + +<ul> + <li> <code><strong>"nonzero</strong></code>": <a href="http://en.wikipedia.org/wiki/Nonzero-rule">non-zero winding rule</a>, 默认值.</li> + <li><code><strong> "evenodd"</strong></code>: <a href="http://en.wikipedia.org/wiki/Even%E2%80%93odd_rule">even-odd winding rule</a>.</li> +</ul> + +<p>这个例子,我们用填充规则 <code>evenodd</code></p> + +<pre class="brush: js notranslate"><code>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"); +}</code> +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="100" height="100"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("Canvas_fill_rules", "110", "110", "https://mdn.mozillademos.org/files/9855/fill-rule.png")}}</p> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Drawing_shapes", "Web/API/Canvas_API/Tutorial/Drawing_text")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/basic_animations/index.html b/files/zh-cn/web/api/canvas_api/tutorial/basic_animations/index.html new file mode 100644 index 0000000000..531fef8cb1 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/basic_animations/index.html @@ -0,0 +1,718 @@ +--- +title: 基本的动画 +slug: Web/API/Canvas_API/Tutorial/Basic_animations +tags: + - Canvas + - HTML5 + - 动画 + - 图像 + - 教程 + - 进阶 +translation_of: Web/API/Canvas_API/Tutorial/Basic_animations +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Compositing", "Web/API/Canvas_API/Tutorial/Advanced_animations")}}</div> + +<div class="summary"> +<p>由于我们是用 JavaScript 去操控 {{HTMLElement("canvas")}} 对象,这样要实现一些交互动画也是相当容易的。在本章中,我们将看看如何做一些基本的动画。</p> +</div> + +<p>可能最大的限制就是图像一旦绘制出来,它就是一直保持那样了。如果需要移动它,我们不得不对所有东西(包括之前的)进行重绘。重绘是相当费时的,而且性能很依赖于电脑的速度。</p> + +<h2 id="Basic_animation_steps" name="Basic_animation_steps">动画的基本步骤</h2> + +<p>你可以通过以下的步骤来画出一帧:</p> + +<ol> + <li><strong>清空 canvas</strong><br> + 除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 <code>clearRect</code> 方法。</li> + <li><strong>保存 canvas 状态</strong><br> + 如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。</li> + <li><strong>绘制动画图形(animated shapes)</strong><br> + 这一步才是重绘动画帧。</li> + <li><strong>恢复 canvas 状态</strong><br> + 如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。</li> +</ol> + +<h2 id="Controlling_an_animation" name="Controlling_an_animation">操控动画 Controlling an animation</h2> + +<p>在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常,我们仅仅在脚本执行结束后才能看见结果,比如说,在 for 循环里面做完成动画是不太可能的。</p> + +<p>因此, 为了实现动画,我们需要一些可以定时执行重绘的方法。有两种方法可以实现这样的动画操控。首先可以通过 <code>setInterval</code> 和 <code>setTimeout</code> 方法来控制在设定的时间点上执行重绘。</p> + +<h3 id="有安排的更新画布_Scheduled_updates"><strong>有安排的更新画布 </strong>Scheduled updates</h3> + +<p>首先,可以用{{domxref("window.setInterval()")}}, {{domxref("window.setTimeout()")}},和{{domxref("window.requestAnimationFrame()")}}来设定定期执行一个指定函数。</p> + +<dl> + <dt>{{domxref("WindowTimers.setInterval", "setInterval(function, delay)")}}</dt> + <dd>当设定好间隔时间后,function会定期执行。</dd> + <dt>{{domxref("WindowTimers.setTimeout", "setTimeout(function, delay)")}}</dt> + <dt>在设定好的时间之后执行函数</dt> + <dd></dd> + <dt>{{domxref("Window.requestAnimationFrame()", "requestAnimationFrame(callback)")}}</dt> + <dd>告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。</dd> +</dl> + +<p>如果你并不需要与用户互动,你可以使用setInterval()方法,它就可以定期执行指定代码。如果我们需要做一个游戏,我们可以使用键盘或者鼠标事件配合上setTimeout()方法来实现。通过设置事件监听,我们可以捕捉用户的交互,并执行相应的动作。</p> + +<div class="note"> +<p>下面的例子,采用 {{domxref("window.requestAnimationFrame()")}}实现动画效果。这个方法提供了更加平缓并更加有效率的方式来执行动画,当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟回调函数执行60次,也有可能会被降低。想要了解更多关于动画循环的信息,尤其是游戏,可以在<a href="https://developer.mozilla.org/zh-CN/docs/Games">Game development zone</a> 参考这篇文章 <a href="https://developer.mozilla.org/zh-CN/docs/Games/Anatomy">Anatomy of a video game</a>。</p> +</div> + +<h2 id="太阳系的动画">太阳系的动画</h2> + +<p>这个例子里面,我会做一个小型的太阳系模拟动画。</p> + +<pre class="brush: js">var sun = new Image(); +var moon = new Image(); +var earth = new Image(); +function init(){ + sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png'; + moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png'; + earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png'; + window.requestAnimationFrame(draw); +} + +function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + ctx.globalCompositeOperation = 'destination-over'; + ctx.clearRect(0,0,300,300); // clear canvas + + ctx.fillStyle = 'rgba(0,0,0,0.4)'; + ctx.strokeStyle = 'rgba(0,153,255,0.4)'; + ctx.save(); + ctx.translate(150,150); + + // Earth + var time = new Date(); + ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() ); + ctx.translate(105,0); + ctx.fillRect(0,-12,50,24); // Shadow + ctx.drawImage(earth,-12,-12); + + // Moon + ctx.save(); + ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() ); + ctx.translate(0,28.5); + ctx.drawImage(moon,-3.5,-3.5); + ctx.restore(); + + ctx.restore(); + + ctx.beginPath(); + ctx.arc(150,150,105,0,Math.PI*2,false); // Earth orbit + ctx.stroke(); + + ctx.drawImage(sun,0,0,300,300); + + window.requestAnimationFrame(draw); +} + +init(); +</pre> + +<div class="hidden"> +<pre class="brush: html"><canvas id="canvas" width="300" height="300"></canvas></pre> +</div> + +<p>{{EmbedLiveSample("太阳系的动画", "310", "310", "https://mdn.mozillademos.org/files/202/Canvas_animation1.png")}}</p> + +<h2 id="动画时钟">动画时钟</h2> + +<p>这个例子实现一个动态时钟, 可以显示当前时间。</p> + +<pre class="brush: js">function clock(){ + var now = new Date(); + var ctx = document.getElementById('canvas').getContext('2d'); + ctx.save(); + ctx.clearRect(0,0,150,150); + ctx.translate(75,75); + ctx.scale(0.4,0.4); + ctx.rotate(-Math.PI/2); + ctx.strokeStyle = "black"; + ctx.fillStyle = "white"; + ctx.lineWidth = 8; + ctx.lineCap = "round"; + + // Hour marks + ctx.save(); + for (var i=0;i<12;i++){ + ctx.beginPath(); + ctx.rotate(Math.PI/6); + ctx.moveTo(100,0); + ctx.lineTo(120,0); + ctx.stroke(); + } + ctx.restore(); + + // Minute marks + ctx.save(); + ctx.lineWidth = 5; + for (i=0;i<60;i++){ + if (i%5!=0) { + ctx.beginPath(); + ctx.moveTo(117,0); + ctx.lineTo(120,0); + ctx.stroke(); + } + ctx.rotate(Math.PI/30); + } + ctx.restore(); + + var sec = now.getSeconds(); + var min = now.getMinutes(); + var hr = now.getHours(); + hr = hr>=12 ? hr-12 : hr; + + ctx.fillStyle = "black"; + + // write Hours + ctx.save(); + ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec ) + ctx.lineWidth = 14; + ctx.beginPath(); + ctx.moveTo(-20,0); + ctx.lineTo(80,0); + ctx.stroke(); + ctx.restore(); + + // write Minutes + ctx.save(); + ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec ) + ctx.lineWidth = 10; + ctx.beginPath(); + ctx.moveTo(-28,0); + ctx.lineTo(112,0); + ctx.stroke(); + ctx.restore(); + + // Write seconds + ctx.save(); + ctx.rotate(sec * Math.PI/30); + ctx.strokeStyle = "#D40000"; + ctx.fillStyle = "#D40000"; + ctx.lineWidth = 6; + ctx.beginPath(); + ctx.moveTo(-30,0); + ctx.lineTo(83,0); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(0,0,10,0,Math.PI*2,true); + ctx.fill(); + ctx.beginPath(); + ctx.arc(95,0,10,0,Math.PI*2,true); + ctx.stroke(); + ctx.fillStyle = "rgba(0,0,0,0)"; + ctx.arc(0,0,3,0,Math.PI*2,true); + ctx.fill(); + ctx.restore(); + + ctx.beginPath(); + ctx.lineWidth = 14; + ctx.strokeStyle = '#325FA2'; + ctx.arc(0,0,142,0,Math.PI*2,true); + ctx.stroke(); + + ctx.restore(); + + window.requestAnimationFrame(clock); +} + +window.requestAnimationFrame(clock); +</pre> + +<div class="hidden"> +<pre class="brush: html"><canvas id="canvas" width="150" height="150"></canvas></pre> +</div> + +<p>{{EmbedLiveSample("动画时钟", "180", "180", "https://mdn.mozillademos.org/files/203/Canvas_animation2.png")}}</p> + +<h2 id="循环全景照片">循环全景照片</h2> + +<p>在这个例子中,会有一个自左向右滑动的全景图。我们使用了在维基百科中找到的<a href="https://mdn.mozillademos.org/files/4553/Capitan_Meadows,_Yosemite_National_Park.jpg">尤塞米提国家公园的图片</a>,当然你可以随意找一张任何尺寸大于canvas的图片。</p> + +<pre class="brush: js">var img = new Image(); + +// User Variables - customize these to change the image being scrolled, its +// direction, and the speed. + +img.src = 'https://mdn.mozillademos.org/files/4553/Capitan_Meadows,_Yosemite_National_Park.jpg'; +var CanvasXSize = 800; +var CanvasYSize = 200; +var speed = 30; // lower is faster +var scale = 1.05; +var y = -4.5; // vertical offset + +// Main program + +var dx = 0.75; +var imgW; +var imgH; +var x = 0; +var clearX; +var clearY; +var ctx; + +img.onload = function() { + imgW = img.width * scale; + imgH = img.height * scale; + + if (imgW > CanvasXSize) { + // image larger than canvas + x = CanvasXSize - imgW; + } + if (imgW > CanvasXSize) { + // image width larger than canvas + clearX = imgW; + } else { + clearX = CanvasXSize; + } + if (imgH > CanvasYSize) { + // image height larger than canvas + clearY = imgH; + } else { + clearY = CanvasYSize; + } + + // get canvas context + ctx = document.getElementById('canvas').getContext('2d'); + + // set refresh rate + return setInterval(draw, speed); +} + +function draw() { + ctx.clearRect(0, 0, clearX, clearY); // clear the canvas + + // if image is <= Canvas Size + if (imgW <= CanvasXSize) { + // reset, start from beginning + if (x > CanvasXSize) { + x = -imgW + x; + } + // draw additional image1 + if (x > 0) { + ctx.drawImage(img, -imgW + x, y, imgW, imgH); + } + // draw additional image2 + if (x - imgW > 0) { + ctx.drawImage(img, -imgW * 2 + x, y, imgW, imgH); + } + } + + // image is > Canvas Size + else { + // reset, start from beginning + if (x > (CanvasXSize)) { + x = CanvasXSize - imgW; + } + // draw aditional image + if (x > (CanvasXSize-imgW)) { + ctx.drawImage(img, x - imgW + 1, y, imgW, imgH); + } + } + // draw image + ctx.drawImage(img, x, y,imgW, imgH); + // amount to move + x += dx; +}</pre> + +<div class="hidden"> +<pre class="brush: html"><canvas id="canvas" width="800" height="200"></canvas></pre> +</div> + +<p>下方就是是图片在其中滑动的 {{HTMLElement("canvas")}}。需要注意的是这里定义的width和height必须与JavaScript代码中的变量值<code>CanvasXZSize</code>和<code>CanvasYSize</code>保持一致。 </p> + +<pre><canvas id="canvas" width="800" height="200"></canvas></pre> + +<p>{{EmbedLiveSample("循环全景照片", "830", "230")}}</p> + +<h2 id="鼠标追踪动画">鼠标追踪动画</h2> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <title>Document</title> + <script> + var cn; + //= document.getElementById('cw'); + var c; + var u = 10; + const m = { + x: innerWidth / 2, + y: innerHeight / 2 + }; + window.onmousemove = function(e) { + m.x = e.clientX; + m.y = e.clientY; + + } + function gc() { + var s = "0123456789ABCDEF"; + var c = "#"; + for (var i = 0; i < 6; i++) { + c += s[Math.ceil(Math.random() * 15)] + } + return c + } + var a = []; + window.onload = function myfunction() { + cn = document.getElementById('cw'); + c = cn.getContext('2d'); + + for (var i = 0; i < 10; i++) { + var r = 30; + var x = Math.random() * (innerWidth - 2 * r) + r; + var y = Math.random() * (innerHeight - 2 * r) + r; + var t = new ob(innerWidth / 2,innerHeight / 2,5,"red",Math.random() * 200 + 20,2); + a.push(t); + } + //cn.style.backgroundColor = "#700bc8"; + + c.lineWidth = "2"; + c.globalAlpha = 0.5; + resize(); + anim() + } + window.onresize = function() { + + resize(); + + } + function resize() { + cn.height = innerHeight; + cn.width = innerWidth; + for (var i = 0; i < 101; i++) { + var r = 30; + var x = Math.random() * (innerWidth - 2 * r) + r; + var y = Math.random() * (innerHeight - 2 * r) + r; + a[i] = new ob(innerWidth / 2,innerHeight / 2,4,gc(),Math.random() * 200 + 20,0.02); + + } + // a[0] = new ob(innerWidth / 2, innerHeight / 2, 40, "red", 0.05, 0.05); + //a[0].dr(); + } + function ob(x, y, r, cc, o, s) { + this.x = x; + this.y = y; + this.r = r; + this.cc = cc; + this.theta = Math.random() * Math.PI * 2; + this.s = s; + this.o = o; + this.t = Math.random() * 150; + + this.o = o; + this.dr = function() { + const ls = { + x: this.x, + y: this.y + }; + this.theta += this.s; + this.x = m.x + Math.cos(this.theta) * this.t; + this.y = m.y + Math.sin(this.theta) * this.t; + c.beginPath(); + c.lineWidth = this.r; + c.strokeStyle = this.cc; + c.moveTo(ls.x, ls.y); + c.lineTo(this.x, this.y); + c.stroke(); + c.closePath(); + + } + } + function anim() { + requestAnimationFrame(anim); + c.fillStyle = "rgba(0,0,0,0.05)"; + c.fillRect(0, 0, cn.width, cn.height); + a.forEach(function(e, i) { + e.dr(); + }); + + } + </script> + <style> + #cw { + position: fixed; + z-index: -1; + } + + body { + margin: 0; + padding: 0; + background-color: rgba(0,0,0,0.05); + } + </style> + </head> + <body> + <canvas id="cw"></canvas> + qwerewr + + </body> +</html> +</pre> + +<h5 id="OutPut">OutPut</h5> + +<table class="standard-table"> + <tbody> + <tr> + <td> + <p><a href="https://kunalverma94.github.io/gallery/gags/beyblade.html"><img alt="beyblade" src="https://kunalverma94.github.io/gallery/beyblade.jpg" style="height: 298px; width: 399px;"></a></p> + </td> + </tr> + </tbody> +</table> + +<h2 id="Snake_Game">Snake Game</h2> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <title>Nokia 1100:snake..Member berries</title> +</head> + +<body> + <div class="keypress hide"> + <div class="up" onclick="emit(38)">&#8593;</div> + <div class="right" onclick="emit(39)">&#8594;</div> + <div class="left" onclick="emit(37)">&#8592;</div> + <div class="down" onclick="emit(40)">&#8595;</div> + </div> + <div class="banner" id="selector"> + <div> + Time :<span id="time">0</span> + </div> + <div>LousyGames ©</div> + <div> + Score :<span id="score">0</span> + </div> + <div class="touch off" onclick="touch(this)">touch</div> + </div> + <canvas id="main"></canvas> +</body> +<style> + body { + margin: 0; + overflow: hidden; + background: #000 + } + + .banner { + text-align: center; + color: #fff; + background: #3f51b5; + line-height: 29px; + position: fixed; + left: 0; + top: 0; + right: 0; + font-family: monospace; + height: 30px; + opacity: .4; + display: flex; + transition: .5s + } + + .banner:hover { + opacity: 1 + } + + div#selector>div { + flex-basis: 30% + } + + @keyframes diss { + from { + opacity: 1 + } + + to { + opacity: 0 + } + } + + .keypress>div { + border: dashed 3px #fff; + height: 48%; + width: 48%; + display: flex; + align-content: center; + justify-content: center; + align-self: center; + align-items: center; + font-size: -webkit-xxx-large; + font-weight: 900; + color: #fff; + transition: .5s; + opacity: .1; + border-radius: 7px + } + + .keypress { + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + display: flex; + flex-wrap: wrap; + justify-content: space-around; + opacity: 1; + user-select: none + } + + .keypress>div:hover { + opacity: 1 + } + + .touch { + background: #8bc34a + } + + .off { + background: #f44336 + } + + .hide { + opacity: 0 + } +</style> +</html></pre> + +<p>Javascript</p> + +<pre class="brush: js">function tmz() { + var e = new Date(t), + i = new Date, + n = Math.abs(i.getMinutes() - e.getMinutes()), + o = Math.abs(i.getSeconds() - e.getSeconds()); + return n + " : " + o + } + + function coll(t, e) { + return t.x < e.x + e.w && t.x + t.w > e.x && t.y < e.y + e.h && t.h + t.y > e.y + } + + function snake() { + this.w = 15, this.h = 15, this.dx = 1, this.dy = 1, this.xf = 1, this.yf = 1, this.sn = []; + for (var t = { + x: w / 2, + y: h / 2 + }, e = 0; e < 5; e++) this.sn.push(Object.assign({}, t)), t.x += this.w; + this.draw = function () { + var t = d && d.search("Arrow") > -1, + e = -1; + if (t) { + var i = { + ...this.sn[0] + }; + if ("ArrowUp" == d && (i.y -= this.h), "ArrowDown" == d && (i.y += this.h), "ArrowLeft" == d && (i.x -= this.w), "ArrowRight" == d && (i.x += this.w), i.x >= w ? i.x = 0 : i.x < 0 && (i.x = w - this.w), i.y > h ? i.y = 0 : i.y < 0 && (i.y = h), e = fa.findIndex(t => coll({ + ...this.sn[0], + h: this.h, + w: this.w + }, t)), this.sn.unshift(i), -1 != e) return console.log(e), fa[e].renew(), void (document.getElementById("score").innerText = Number(document.getElementById("score").innerText) + 1); + this.sn.pop(), console.log(6) + } + this.sn.forEach((t, e, i) => { + if (0 == e || i.length - 1 == e) { + var n = c.createLinearGradient(t.x, t.y, t.x + this.w, t.y + this.h); + i.length - 1 == e ? (n.addColorStop(0, "black"), n.addColorStop(1, "#8BC34A")) : (n.addColorStop(0, "#8BC34A"), n.addColorStop(1, "white")), c.fillStyle = n + } else c.fillStyle = "#8BC34A"; + c.fillRect(t.x, t.y, this.w, this.h), c.strokeStyle = "#E91E63", c.font = "30px serif", c.strokeStyle = "#9E9E9E", i.length - 1 != e && 0 != e && c.strokeRect(t.x, t.y, this.w, this.h), 0 == e && (c.beginPath(), c.fillStyle = "#F44336", c.arc(t.x + 10, t.y + 2, 5, 360, 0), c.fill()), c.arc(t.x + 10, t.y + 2, 5, 360, 0), c.fill(), c.beginPath() + }) + } + } + + function gc() { + for (var t = "0123456789ABCDEF", e = "#", i = 0; i < 6; i++) e += t[Math.ceil(15 * Math.random())]; + return e + } + + function food() { + this.x = 0, this.y = 0, this.b = 10, this.w = this.b, this.h = this.b, this.color = gc(), this.renew = function () { + this.x = Math.floor(Math.random() * (w - 200) + 10), this.y = Math.floor(Math.random() * (h - 200) + 30), this.color = gc() + }, this.renew(), this.put = (() => { + c.fillStyle = this.color, c.arc(this.x, this.y, this.b - 5, 0, 2 * Math.PI), c.fill(), c.beginPath(), c.arc(this.x, this.y, this.b - 5, 0, Math.PI), c.strokeStyle = "green", c.lineWidth = 10, c.stroke(), c.beginPath(), c.lineWidth = 1 + }) + } + + function init() { + cc.height = h, cc.width = w, c.fillRect(0, 0, w, innerHeight); + for (var t = 0; t < 10; t++) fa.push(new food); + s = new snake(w / 2, h / 2, 400, 4, 4), anima() + } + + function anima() { + c.fillStyle = "rgba(0,0,0,0.11)", c.fillRect(0, 0, cc.width, cc.height), fa.forEach(t => t.put()), s.draw(), document.getElementById("time").innerText = tmz(), setTimeout(() => { + requestAnimationFrame(anima) + }, fw) + } + + function emit(t) { + key.keydown(t) + } + + function touch(t) { + t.classList.toggle("off"), document.getElementsByClassName("keypress")[0].classList.toggle("hide") + } + var t = new Date + "", + d = void 0, + cc = document.getElementsByTagName("canvas")[0], + c = cc.getContext("2d"); + key = {}, key.keydown = function (t) { + var e = document.createEvent("KeyboardEvent"); + Object.defineProperty(e, "keyCode", { + get: function () { + return this.keyCodeVal + } + }), Object.defineProperty(e, "key", { + get: function () { + return 37 == this.keyCodeVal ? "ArrowLeft" : 38 == this.keyCodeVal ? "ArrowUp" : 39 == this.keyCodeVal ? "ArrowRight" : "ArrowDown" + } + }), Object.defineProperty(e, "which", { + get: function () { + return this.keyCodeVal + } + }), e.initKeyboardEvent ? e.initKeyboardEvent("keydown", !0, !0, document.defaultView, !1, !1, !1, !1, t, t) : e.initKeyEvent("keydown", !0, !0, document.defaultView, !1, !1, !1, !1, t, 0), e.keyCodeVal = t, e.keyCode !== t && alert("keyCode mismatch " + e.keyCode + "(" + e.which + ")"), document.dispatchEvent(e) + }; + var o, s, h = innerHeight, + w = innerWidth, + fw = 60, + fa = []; + window.onkeydown = function (t) { + var e = t.key; + (e.search("Arrow") > -1 || "1" == e) && (d = t.key), "i" != e && "I" != e || (console.log("inc"), fw -= 10), "d" != e && "D" != e || (console.log("dec"), fw += 10) + }, init(); +</pre> + +<h5 id="Output">Output</h5> + +<table class="standard-table"> + <tbody> + <tr> + <td> + <h2 id="sect1"><a href="https://kunalverma94.github.io/pokemon/snake.html"><img alt="Snake game" src="https://kunalverma94.github.io/view/images/snake.jpg" style="height: 400px; width: 600px;"></a></h2> + </td> + </tr> + </tbody> +</table> + +<h2 id="Other_examples" name="Other_examples">其它例子</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/A_basic_ray-caster" title="/zh-CN/docs/Web/Guide/HTML/A_basic_ray-caster">A basic ray-caster</a></dt> + <dd>一个关于如何使用键盘关联控制动画的优秀的案例。</dd> + <dt><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Advanced_animations">Advanced animations</a></dt> + <dd>我们将在下一章看到一些先进的动画技术和物理现象。</dd> +</dl> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Compositing", "Web/API/Canvas_API/Tutorial/Advanced_animations")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/basic_usage/index.html b/files/zh-cn/web/api/canvas_api/tutorial/basic_usage/index.html new file mode 100644 index 0000000000..a186a0fc3d --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/basic_usage/index.html @@ -0,0 +1,152 @@ +--- +title: Canvas的基本用法 +slug: Web/API/Canvas_API/Tutorial/Basic_usage +tags: + - Canvas + - HTML + - 中级 + - 图像 + - 教程 +translation_of: Web/API/Canvas_API/Tutorial/Basic_usage +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial", "Web/API/Canvas_API/Tutorial/Drawing_shapes")}}</div> + +<div class="summary"> +<p>让我们通过了解 {{HTMLElement("canvas")}} {{Glossary("HTML")}} 元素本身开始本教程。在本页结束时,你会了解到如何去设置一个 canvas 2D 上下文以及如何在你的浏览器上创建第一个例子。</p> +</div> + +<h2 id="<canvas>_元素"> <code><canvas></code> 元素</h2> + +<pre class="brush: html"><canvas id="tutorial" width="150" height="150"></canvas> +</pre> + +<p>{{HTMLElement("canvas")}} 看起来和 {{HTMLElement("img")}} 元素很相像,唯一的不同就是它并没有 src 和 alt 属性。实际上,<code><canvas></code> 标签只有两个属性<strong>——</strong> {{htmlattrxref("width", "canvas")}}和{{htmlattrxref("height", "canvas")}}。这些都是可选的,并且同样利用 {{Glossary("DOM")}} <a href="/zh-CN/docs/Web/API/HTMLCanvasElement">properties</a> 来设置。当没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素。该元素可以使用{{Glossary("CSS")}}来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:<span class="short_text" id="result_box" lang="zh-CN"><span>如果</span><span>CSS</span></span><span class="gt-baf-back">的尺寸</span><span class="short_text" lang="zh-CN"><span>与</span><span>初始</span><span>画布的</span><span>比例不一致</span><span>,</span><span>它会出现</span><span>扭曲</span><span>。</span></span></p> + +<div class="note"> +<p><strong>注意:</strong> 如果你绘制出来的图像是扭曲的, 尝试用width和height属性为<canvas>明确规定宽高,而不是使用CSS。</p> +</div> + +<p><a href="/zh-CN/docs/Web/HTML/Global_attributes/id"><code>id</code></a>属性并不是<canvas>元素所特有的,而是每一个HTML元素都默认具有的属性(比如class属性)。给每个标签都加上一个id属性是个好主意,因为这样你就能在我们的脚本中很容易的找到它。</p> + +<p><canvas>元素可以像任何一个普通的图像一样(有{{cssxref("margin")}},{{cssxref("border")}},{{cssxref("background")}}等等属性)被设计。然而,这些样式不会影响在canvas中的实际图像。我们将会在一个<span class="short_text" id="result_box" lang="zh-CN"><span><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors">专门的章节</a>里</span></span>看到这是如何解决的。当开始时没有为canvas规定样式规则,其将会完全透明。</p> + +<div id="section_2"> +<h3 id="替换内容">替换内容</h3> + +<p><canvas>元素与{{HTMLElement("img")}}标签的不同之处在于,就像{{HTMLElement("video")}},{{HTMLElement("audio")}},或者 {{HTMLElement("picture")}}元素一样,<span class="short_text" id="result_box" lang="zh-CN"><span>很容易</span><span>定义一些</span><span>替代内容</span></span><span lang="zh-CN"><span>。</span></span>由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者<span class="short_text" id="result_box" lang="zh-CN"><span>文本</span><span>浏览器</span></span>不支持HTML元素"canvas",在这些浏览器上你应该总是能展示替代内容。</p> + +<p>这非常简单:我们只是在<canvas>标签中提供了替换内容。不支持<canvas>的浏览器将会忽略容器并在其中渲染后备内容。而支持<canvas>的浏览器将会忽略在容器中包含的内容,并且只是正常渲染canvas。</p> + +<p>举个例子,我们可以提供对canvas内容的文字描述或者是提供动态生成内容相对应的静态图片,如下所示:</p> + +<pre class="brush: html"><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> +</pre> + +<h3 id="<canvas>_标签不可省"> <code></canvas></code> 标签不可省</h3> + +<p>与 {{HTMLElement("img")}} 元素不同,{{HTMLElement("canvas")}} 元素<strong>需要</strong>结束标签(<code></canvas></code>)。如果结束标签不存在,则文档的其余部分会被认为是替代内容,将不会显示出来。</p> + +<p>如果不需要替代内容,一个简单的 <code><canvas id="foo" ...></canvas> </code>在所有支持canvas的浏览器中都是完全兼容的。</p> + +<h2 id="渲染上下文(The_rendering_context)">渲染上下文(The rendering context)</h2> + +<p>{{HTMLElement("canvas")}} 元素创造了一个固定大小的画布,它公开了一个或多个<strong>渲染上下文</strong>,其可以用来绘制和处理要展示的内容。我们将会将注意力放在2D渲染上下文中。其他种类的上下文也许提供了不同种类的渲染方式;比如, <a href="/en-US/docs/Web/WebGL" title="/en-US/docs/Web/WebGL">WebGL</a> 使用了基于<a class="external" href="http://www.khronos.org/opengles/" rel="external" title="http://en.wikipedia.org/wiki/OpenGL_ES">OpenGL ES</a>的3D上下文 ("experimental-webgl") 。</p> + +<p>canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。{{HTMLElement("canvas")}} 元素有一个叫做 {{domxref("HTMLCanvasElement.getContext", "getContext()")}} 的方法,这个方法是用来获得渲染上下文和它的绘画功能。<code>getContext()</code>只有一个参数,上下文的格式。对于2D图像而言,如本教程,你可以使用 {{domxref("CanvasRenderingContext2D")}}。</p> + +<pre class="brush: js">var canvas = document.getElementById('tutorial'); +var ctx = canvas.getContext('2d'); +</pre> + +<p>代码的第一行通过使用 {{domxref("document.getElementById()")}} 方法来为 {{HTMLElement("canvas")}} 元素得到DOM对象。一旦有了元素对象,你可以通过使用它的getContext() 方法来访问绘画上下文。</p> + +<div id="section_5"> +<h2 id="检查支持性">检查支持性</h2> + +<p>替换内容是用于在不支持 {{HTMLElement("canvas")}} 标签的浏览器中展示的。通过简单的测试<code>getContext()方法的存在,脚本可以检查编程支持性</code>。上面的代码片段现在变成了这个样子:</p> + +<pre class="brush: js">var canvas = document.getElementById('tutorial'); + +if (canvas.getContext){ + var ctx = canvas.getContext('2d'); + // drawing code here +} else { + // canvas-unsupported code here +} +</pre> +</div> +</div> + +<h2 id="一个模板骨架">一个模板骨架</h2> + +<p>这里的是一个最简单的模板,我们之后就可以把它作为之后的例子的起点。</p> + +<div class="note"> +<p><strong>注意: </strong>为了简洁, 我们在HTML中内嵌了script元素, 但并不推荐这种做法。</p> +</div> + +<pre class="brush: html"><html> + <head> + <title>Canvas tutorial</title> + <script type="text/javascript"> + function draw(){ + var canvas = document.getElementById('tutorial'); + if (canvas.getContext){ + var ctx = canvas.getContext('2d'); + } + } + </script> + <style type="text/css"> + canvas { border: 1px solid black; } + </style> + </head> + <body onload="draw();"> + <canvas id="tutorial" width="150" height="150"></canvas> + </body> +</html> +</pre> + +<p>上面的脚本中包含一个叫做draw()的函数,当页面加载结束的时候就会执行这个函数。通过使用在文档上加载事件来完成。只要页面加载结束,这个函数,或者像是这个的,同样可以使用 {{domxref("WindowTimers.setTimeout", "window.setTimeout()")}}, {{domxref("WindowTimers.setInterval", "window.setInterval()")}},或者其他任何事件处理程序来调用。</p> + +<p>模板看起来会是这样。<span class="short_text" id="result_box" lang="zh-CN"><span>如这里所示</span><span>,</span><span>它</span><span>最初是</span><span>空白的</span><span>。</span></span></p> + +<p>{{EmbedLiveSample("%E4%B8%80%E4%B8%AA%E6%A8%A1%E6%9D%BF%E9%AA%A8%E6%9E%B6", 160, 160)}}</p> + +<h2 id="一个简单例子">一个简单例子</h2> + +<p>一开始,让我们来看个简单的例子,我们绘制了两个有趣的长方形,其中的一个有着alpha透明度。我们将在接下来的例子里深入探索一下这是如何工作的。</p> + +<pre class="brush: html"><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> +</pre> + +<p>例子看起来像是这样::</p> + +<p>{{EmbedLiveSample("%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E4%BE%8B%E5%AD%90", 160, 160, "https://mdn.mozillademos.org/files/228/canvas_ex1.png")}}</p> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial", "Web/API/Canvas_API/Tutorial/Drawing_shapes")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/compositing/example/index.html b/files/zh-cn/web/api/canvas_api/tutorial/compositing/example/index.html new file mode 100644 index 0000000000..934fa4e2f9 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/compositing/example/index.html @@ -0,0 +1,295 @@ +--- +title: Compositing 示例 +slug: Web/API/Canvas_API/Tutorial/Compositing/Example +tags: + - Canvas + - Example + - Graphics + - HTML + - HTML5 + - Tutorial +translation_of: Web/API/Canvas_API/Tutorial/Compositing/Example +--- +<div>{{CanvasSidebar}}</div> + +<p>这个案例程序演示了一些<a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.globalCompositeOperation">图像合成操作</a>,其结果如下所示:</p> + +<p>{{EmbedLiveSample("合成示例", "100%", 7250)}}</p> + +<h2 id="合成示例">合成示例</h2> + +<p>下面的代码定义了一些全局变量,可用于程序的其他部分。</p> + +<pre class="brush: js">var canvas1 = document.createElement("canvas"); +var canvas2 = document.createElement("canvas"); +var gco = [ 'source-over','source-in','source-out','source-atop', + 'destination-over','destination-in','destination-out','destination-atop', + 'lighter', 'copy','xor', 'multiply', 'screen', 'overlay', 'darken', + 'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light', + 'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity' + ].reverse(); +var gcoText = [ +'这是默认设置,并在现有画布上下文之上绘制新图形。', +'新图形只在新图形和目标画布重叠的地方绘制。其他的都是透明的。', +'在不与现有画布内容重叠的地方绘制新图形。', +'新图形只在与现有画布内容重叠的地方绘制。', +'在现有的画布内容后面绘制新的图形。', +'现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。', +'现有内容保持在新图形不重叠的地方。', +'现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。', +'两个重叠图形的颜色是通过颜色值相加来确定的。', +'只显示新图形。', +'图像中,那些重叠和正常绘制之外的其他地方是透明的。', +'将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。', +'像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。', +'multiply和screen的结合,原本暗的地方更暗,原本亮的地方更亮。', +'保留两个图层中最暗的像素。', +'保留两个图层中最亮的像素。', +'将底层除以顶层的反置。', +'将反置的底层除以顶层,然后将结果反过来。', +'屏幕相乘(A combination of multiply and screen)类似于叠加,但上下图层互换了。', +'用顶层减去底层或者相反来得到一个正值。', +'一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白。', +'和difference相似,但对比度较低。', +'保留了底层的亮度(luma)和色度(chroma),同时采用了顶层的色调(hue)。', +'保留底层的亮度(luma)和色调(hue),同时采用顶层的色度(chroma)。', +'保留了底层的亮度(luma),同时采用了顶层的色调(hue)和色度(chroma)。', +'保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma)。' + ].reverse(); +var width = 320; +var height = 340; +</pre> + +<h3 id="主程序">主程序</h3> + +<p>当页面加载时,这部分程序会运行,并执行示例代码:</p> + +<pre class="brush: js">window.onload = function() { + // lum in sRGB + var lum = { + r: 0.33, + g: 0.33, + b: 0.33 + }; + // resize canvas + canvas1.width = width; + canvas1.height = height; + canvas2.width = width; + canvas2.height = height; + lightMix() + colorSphere(); + runComposite(); + return; +}; +</pre> + +<p>这部分代码,,<code>runComposite()</code>,处理了大部分的工作,但需要依赖于许多效用函数来完成复杂的处理工作。</p> + +<pre class="brush: js">function createCanvas() { + var canvas = document.createElement("canvas"); + canvas.style.background = "url("+op_8x8.data+")"; + canvas.style.border = "1px solid #000"; + canvas.style.margin = "5px"; + canvas.width = width/2; + canvas.height = height/2; + return canvas; +} + +function runComposite() { + var dl = document.createElement("dl"); + document.body.appendChild(dl); + while(gco.length) { + var pop = gco.pop(); + var dt = document.createElement("dt"); + dt.textContent = pop; + dl.appendChild(dt); + var dd = document.createElement("dd"); + var p = document.createElement("p"); + p.textContent = gcoText.pop(); + dd.appendChild(p); + + var canvasToDrawOn = createCanvas(); + var canvasToDrawFrom = createCanvas(); + var canvasToDrawResult = createCanvas(); + + var ctx = canvasToDrawResult.getContext('2d'); + ctx.clearRect(0, 0, width, height) + ctx.save(); + ctx.drawImage(canvas1, 0, 0, width/2, height/2); + ctx.globalCompositeOperation = pop; + ctx.drawImage(canvas2, 0, 0, width/2, height/2); + ctx.globalCompositeOperation = "source-over"; + ctx.fillStyle = "rgba(0,0,0,0.8)"; + ctx.fillRect(0, height/2 - 20, width/2, 20); + ctx.fillStyle = "#FFF"; + ctx.font = "14px arial"; + ctx.fillText(pop, 5, height/2 - 5); + ctx.restore(); + + var ctx = canvasToDrawOn.getContext('2d'); + ctx.clearRect(0, 0, width, height) + ctx.save(); + ctx.drawImage(canvas1, 0, 0, width/2, height/2); + ctx.fillStyle = "rgba(0,0,0,0.8)"; + ctx.fillRect(0, height/2 - 20, width/2, 20); + ctx.fillStyle = "#FFF"; + ctx.font = "14px arial"; + ctx.fillText('existing content', 5, height/2 - 5); + ctx.restore(); + + var ctx = canvasToDrawFrom.getContext('2d'); + ctx.clearRect(0, 0, width, height) + ctx.save(); + ctx.drawImage(canvas2, 0, 0, width/2, height/2); + ctx.fillStyle = "rgba(0,0,0,0.8)"; + ctx.fillRect(0, height/2 - 20, width/2, 20); + ctx.fillStyle = "#FFF"; + ctx.font = "14px arial"; + ctx.fillText('new content', 5, height/2 - 5); + ctx.restore(); + + dd.appendChild(canvasToDrawOn); + dd.appendChild(canvasToDrawFrom); + dd.appendChild(canvasToDrawResult); + + dl.appendChild(dd); + } +}; +</pre> + +<h3 id="效用函数(Utility_functions)">效用函数(Utility functions)</h3> + +<p>程序需要依赖许多效用函数。</p> + +<pre class="brush: js">var lightMix = function() { + var ctx = canvas2.getContext("2d"); + ctx.save(); + ctx.globalCompositeOperation = "lighter"; + ctx.beginPath(); + ctx.fillStyle = "rgba(255,0,0,1)"; + ctx.arc(100, 200, 100, Math.PI*2, 0, false); + ctx.fill() + ctx.beginPath(); + ctx.fillStyle = "rgba(0,0,255,1)"; + ctx.arc(220, 200, 100, Math.PI*2, 0, false); + ctx.fill() + ctx.beginPath(); + ctx.fillStyle = "rgba(0,255,0,1)"; + ctx.arc(160, 100, 100, Math.PI*2, 0, false); + ctx.fill(); + ctx.restore(); + ctx.beginPath(); + ctx.fillStyle = "#f00"; + ctx.fillRect(0,0,30,30) + ctx.fill(); +}; +</pre> + +<pre class="brush: js">var colorSphere = function(element) { + var ctx = canvas1.getContext("2d"); + var width = 360; + var halfWidth = width / 2; + var rotate = (1 / 360) * Math.PI * 2; // per degree + var offset = 0; // scrollbar offset + var oleft = -20; + var otop = -20; + for (var n = 0; n <= 359; n ++) { + var gradient = ctx.createLinearGradient(oleft + halfWidth, otop, oleft + halfWidth, otop + halfWidth); + var color = Color.HSV_RGB({ H: (n + 300) % 360, S: 100, V: 100 }); + gradient.addColorStop(0, "rgba(0,0,0,0)"); + gradient.addColorStop(0.7, "rgba("+color.R+","+color.G+","+color.B+",1)"); + gradient.addColorStop(1, "rgba(255,255,255,1)"); + ctx.beginPath(); + ctx.moveTo(oleft + halfWidth, otop); + ctx.lineTo(oleft + halfWidth, otop + halfWidth); + ctx.lineTo(oleft + halfWidth + 6, otop); + ctx.fillStyle = gradient; + ctx.fill(); + ctx.translate(oleft + halfWidth, otop + halfWidth); + ctx.rotate(rotate); + ctx.translate(-(oleft + halfWidth), -(otop + halfWidth)); + } + ctx.beginPath(); + ctx.fillStyle = "#00f"; + ctx.fillRect(15,15,30,30) + ctx.fill(); + return ctx.canvas; +}; +</pre> + +<pre class="brush: js">// HSV (1978) = H: Hue / S: Saturation / V: Value +Color = {}; +Color.HSV_RGB = function (o) { + var H = o.H / 360, + S = o.S / 100, + V = o.V / 100, + R, G, B; + var A, B, C, D; + if (S == 0) { + R = G = B = Math.round(V * 255); + } else { + if (H >= 1) H = 0; + H = 6 * H; + D = H - Math.floor(H); + A = Math.round(255 * V * (1 - S)); + B = Math.round(255 * V * (1 - (S * D))); + C = Math.round(255 * V * (1 - (S * (1 - D)))); + V = Math.round(255 * V); + switch (Math.floor(H)) { + case 0: + R = V; + G = C; + B = A; + break; + case 1: + R = B; + G = V; + B = A; + break; + case 2: + R = A; + G = V; + B = C; + break; + case 3: + R = A; + G = B; + B = V; + break; + case 4: + R = C; + G = A; + B = V; + break; + case 5: + R = V; + G = A; + B = B; + break; + } + } + return { + R: R, + G: G, + B: B + }; +}; + +var createInterlace = function (size, color1, color2) { + var proto = document.createElement("canvas").getContext("2d"); + proto.canvas.width = size * 2; + proto.canvas.height = size * 2; + proto.fillStyle = color1; // top-left + proto.fillRect(0, 0, size, size); + proto.fillStyle = color2; // top-right + proto.fillRect(size, 0, size, size); + proto.fillStyle = color2; // bottom-left + proto.fillRect(0, size, size, size); + proto.fillStyle = color1; // bottom-right + proto.fillRect(size, size, size, size); + var pattern = proto.createPattern(proto.canvas, "repeat"); + pattern.data = proto.canvas.toDataURL(); + return pattern; +}; + +var op_8x8 = createInterlace(8, "#FFF", "#eee");</pre> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/compositing/index.html b/files/zh-cn/web/api/canvas_api/tutorial/compositing/index.html new file mode 100644 index 0000000000..941b04593e --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/compositing/index.html @@ -0,0 +1,112 @@ +--- +title: 组合 Compositing +slug: Web/API/Canvas_API/Tutorial/Compositing +tags: + - Canvas + - Graphics + - HTML + - HTML5 + - Intermediate + - Totorial +translation_of: Web/API/Canvas_API/Tutorial/Compositing +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Transformations", "Web/API/Canvas_API/Tutorial/Basic_animations")}}</div> + +<div class="summary"> +<p>在<a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations">之前的例子</a>里面,我们总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。不过,我们可以利用 <code>globalCompositeOperation</code> 属性来改变这种状况。此外, <code>clip</code>属性允许我们隐藏不想看到的部分图形。</p> +</div> + +<h2 id="globalCompositeOperation" name="globalCompositeOperation"><code>globalCompositeOperation</code></h2> + +<p>我们不仅可以在已有图形后面再画新图形,还可以用来遮盖指定区域,清除画布中的某些部分(清除区域不仅限于矩形,像{{domxref("CanvasRenderingContext2D.clearRect", "clearRect()")}}方法做的那样)以及更多其他操作。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.globalCompositeOperation", "globalCompositeOperation = type")}}</dt> + <dd>这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识12种遮盖方式的字符串。</dd> +</dl> + +<p>查看下面<a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing/Example">Compositing 示例</a>的代码。</p> + +<p>{{EmbedLiveSample("合成示例", 750, 6750, "" ,"Web/API/Canvas_API/Tutorial/Compositing/Example")}}</p> + +<h2 id="Clipping_paths" name="Clipping_paths">裁切路径</h2> + +<p><img alt="" class="internal" src="https://mdn.mozillademos.org/files/209/Canvas_clipping_path.png" style="float: right;">裁切路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分。如右图所示。红边五角星就是裁切路径,所有在路径以外的部分都不会在 canvas 上绘制出来。</p> + +<p>如果和上面介绍的 <code>globalCompositeOperation</code> 属性作一比较,它可以实现与 <code>source-in</code> 和 <code>source-atop</code>差不多的效果。最重要的区别是裁切路径不会在 canvas 上绘制东西,而且它永远不受新图形的影响。这些特性使得它在特定区域里绘制图形时相当好用。</p> + +<p>在 <a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes" title="Web/Guide/HTML/Canvas_tutorial/Drawing_shapes#Drawing_paths">绘制图形</a> 一章中,我只介绍了 <code>stroke</code> 和 <code>fill</code> 方法,这里介绍第三个方法<code>clip</code>。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.clip", "clip()")}}</dt> + <dd>将当前正在构建的路径转换为当前的裁剪路径。</dd> +</dl> + +<p>我们使用 <code>clip()</code>方法来创建一个新的裁切路径。</p> + +<p>默认情况下,canvas 有一个与它自身一样大的裁切路径(也就是没有裁切效果)。</p> + +<h3 id="A_clip_example" name="A_clip_example"><code>clip</code> 的例子</h3> + +<p>这个例子,我会用一个圆形的裁切路径来限制随机星星的绘制区域。</p> + +<pre class="brush: js;highlight[9]">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + ctx.fillRect(0,0,150,150); + ctx.translate(75,75); + + // Create a circular clipping path + ctx.beginPath(); + ctx.arc(0,0,60,0,Math.PI*2,true); + ctx.clip(); + + // draw background + var lingrad = ctx.createLinearGradient(0,-75,0,75); + lingrad.addColorStop(0, '#232256'); + lingrad.addColorStop(1, '#143778'); + + ctx.fillStyle = lingrad; + ctx.fillRect(-75,-75,150,150); + + // draw stars + for (var j=1;j<50;j++){ + ctx.save(); + ctx.fillStyle = '#fff'; + ctx.translate(75-Math.floor(Math.random()*150), + 75-Math.floor(Math.random()*150)); + drawStar(ctx,Math.floor(Math.random()*4)+2); + ctx.restore(); + } + +} +function drawStar(ctx,r){ + ctx.save(); + ctx.beginPath() + ctx.moveTo(r,0); + for (var i=0;i<9;i++){ + ctx.rotate(Math.PI/5); + if(i%2 == 0) { + ctx.lineTo((r/0.525731)*0.200811,0); + } else { + ctx.lineTo(r,0); + } + } + ctx.closePath(); + ctx.fill(); + ctx.restore(); +} +</pre> + +<div class="hidden"> +<pre class="brush: html"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js">draw();</pre> +</div> + +<p>首先,我画了一个与 canvas 一样大小的黑色方形作为背景,然后移动原点至中心点。然后用 <code>clip</code> 方法创建一个弧形的裁切路径。裁切路径也属于 canvas 状态的一部分,可以被保存起来。如果我们在创建新裁切路径时想保留原来的裁切路径,我们需要做的就是保存一下 canvas 的状态。</p> + +<p>裁切路径创建之后所有出现在它里面的东西才会画出来。在画线性渐变时我们就会注意到这点。然后会绘制出50 颗随机位置分布(经过缩放)的星星,当然也只有在裁切路径里面的星星才会绘制出来。</p> + +<p>{{EmbedLiveSample("A_clip_example", "180", "180", "https://mdn.mozillademos.org/files/208/Canvas_clip.png")}}</p> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Transformations", "Web/API/Canvas_API/Tutorial/Basic_animations")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/drawing_shapes/index.html b/files/zh-cn/web/api/canvas_api/tutorial/drawing_shapes/index.html new file mode 100644 index 0000000000..66f177213a --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/drawing_shapes/index.html @@ -0,0 +1,583 @@ +--- +title: 使用canvas来绘制图形 +slug: Web/API/Canvas_API/Tutorial/Drawing_shapes +tags: + - Canvas + - HTML Canvas + - HTML5 + - 图形 + - 教程 + - 画布 + - 进阶 +translation_of: Web/API/Canvas_API/Tutorial/Drawing_shapes +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Basic_usage", "Web/API/Canvas_API/Tutorial/Applying_styles_and_colors")}}</div> + +<div class="summary"> +<p>既然我们已经设置了 <a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_usage">canvas 环境</a>,我们可以深入了解如何在 canvas 上绘制。到本文的最后,你将学会如何绘制矩形,三角形,直线,圆弧和曲线,变得熟悉这些基本的形状。绘制物体到Canvas前,需掌握路径,我们看看到底怎么做。</p> +</div> + +<h2 id="栅格">栅格</h2> + +<p><img alt="" src="https://mdn.mozillademos.org/files/224/Canvas_default_grid.png" style="float: right; height: 220px; width: 220px;"></p> + +<p>在我们开始画图之前,我们需要了解一下画布栅格(canvas grid)以及坐标空间。上一页中的HTML模板中有个宽150px, 高150px的canvas元素。如右图所示,canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。在课程的最后我们会平移原点到不同的坐标上,旋转网格以及缩放。现在我们还是使用原来的设置。</p> + +<div> +<h2 id="绘制矩形">绘制矩形</h2> + +<p>不同于 {{Glossary("SVG")}},{{HTMLElement("canvas")}} 只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线段)。所有其他类型的图形都是通过一条或者多条路径组合而成的。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。</p> + +<div id="section_3"> +<p>首先,我们回到矩形的绘制中。canvas提供了三种方法绘制矩形:</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.fillRect", "fillRect(x, y, width, height)")}}</dt> + <dd>绘制一个填充的矩形</dd> + <dt>{{domxref("CanvasRenderingContext2D.strokeRect", "strokeRect(x, y, width, height)")}}</dt> + <dd>绘制一个矩形的边框</dd> + <dt>{{domxref("CanvasRenderingContext2D.clearRect", "clearRect(x, y, width, height)")}}</dt> + <dd>清除指定矩形区域,让清除部分完全透明。</dd> +</dl> + +<p>上面提供的方法之中每一个都包含了相同的参数。x与y指定了在canvas画布上所绘制的矩形的左上角(相对于原点)的坐标。width和height设置矩形的尺寸。</p> + +<p>下面的draw() 函数是前一页中取得的,现在就来使用上面的三个函数。</p> + +<h3 id="矩形(Rectangular)例子">矩形(Rectangular)例子</h3> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">function draw() { + var canvas = document.getElementById('canvas'); + if (canvas.getContext) { + var ctx = canvas.getContext('2d'); + + ctx.fillRect(25, 25, 100, 100); + ctx.clearRect(45, 45, 60, 60); + ctx.strokeRect(50, 50, 50, 50); + } +}</pre> + +<p>该例子的输出如下图所示。</p> + +<p>{{EmbedLiveSample("矩形(Rectangular)例子", 160, 160, "https://mdn.mozillademos.org/files/245/Canvas_rect.png")}}</p> + +<p><code>fillRect()</code>函数绘制了一个边长为100px的黑色正方形。<code>clearRect()</code>函数从正方形的中心开始擦除了一个60*60px的正方形,接着<code>strokeRect()</code>在清除区域内生成一个50*50的正方形边框。</p> + +<p>接下来我们能够看到clearRect()的两个可选方法,然后我们会知道如何改变渲染图形的填充颜色及描边颜色。</p> + +<p>不同于下一节所要介绍的路径函数(path function),以上的三个函数绘制之后会马上显现在canvas上,即时生效。</p> + +<h2 id="绘制路径">绘制路径</h2> + +<p>图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤。</p> + +<ol> + <li>首先,你需要创建路径起始点。</li> + <li>然后你使用<a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D#Paths">画图命令</a>去画出路径。</li> + <li>之后你把路径封闭。</li> + <li>一旦路径生成,你就能通过描边或填充路径区域来渲染图形。</li> +</ol> + +<p>以下是所要用到的函数:</p> + +<dl> + <dt><code>beginPath()</code></dt> + <dd>新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。</dd> + <dt><code>closePath()</code></dt> + <dd>闭合路径之后图形绘制命令又重新指向到上下文中。</dd> + <dt><code>stroke()</code></dt> + <dd>通过线条来绘制图形轮廓。</dd> + <dt><code>fill()</code></dt> + <dd>通过填充路径的内容区域生成实心的图形。</dd> +</dl> + +<p>生成路径的第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。</p> + +<div class="note"><strong>注意:当前路径为空,即调用beginPath()之后,或者canvas刚建的时候,第一条路径构造命令通常被视为是moveTo(),无论实际上是什么。出于这个原因,你几乎总是要在设置路径之后专门指定你的起始位置。</strong></div> + +<p>第二步就是调用函数指定绘制路径,本文稍后我们就能看到了。</p> + +<p>第三,就是闭合路径closePath(),不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。</p> + +<div class="note"><strong>注意:当你调用fill()函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath()函数。但是调用stroke()时不会自动闭合</strong><strong style="line-height: 1.5;">。</strong></div> + +<h3 id="绘制一个三角形">绘制一个三角形</h3> + +<p>例如,绘制三角形的代码如下:</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="100" height="100"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">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(); + } +} +</pre> + +<p>输出看上去如下:</p> + +<p>{{EmbedLiveSample("绘制一个三角形", 110, 110, "https://mdn.mozillademos.org/files/9847/triangle.png")}}</p> + +<h3 id="移动笔触">移动笔触</h3> + +<p>一个非常有用的函数,而这个函数实际上并不能画出任何东西,也是上面所描述的路径列表的一部分,这个函数就是<code>moveTo()</code>。或者你可以想象一下在纸上作业,一支钢笔或者铅笔的笔尖从一个点到另一个点的移动过程。</p> + +<dl> + <dt><code>moveTo(<em>x</em>, <em>y</em>)</code></dt> + <dd>将笔触移动到指定的坐标x以及y上。</dd> +</dl> + +<p>当canvas初始化或者<code>beginPath()</code>调用后,你通常会使用<code>moveTo()</code>函数设置起点。我们也能够使用<code>moveTo()</code>绘制一些不连续的路径。看一下下面的笑脸例子。我将用到<code>moveTo()</code>方法(红线处)的地方标记了。</p> + +<p>你可以尝试一下,使用下边的代码片。只需要将其复制到之前的<code>draw()</code>函数即可。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">function draw() { + var canvas = document.getElementById('canvas'); + if (canvas.getContext){ + var ctx = canvas.getContext('2d'); + + ctx.beginPath(); + ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制 + ctx.moveTo(110, 75); + ctx.arc(75, 75, 35, 0, Math.PI, false); // 口(顺时针) + ctx.moveTo(65, 65); + ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼 + ctx.moveTo(95, 65); + ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼 + ctx.stroke(); + } +} +</pre> + +<p>结果看起来是这样的:</p> + +<p>{{EmbedLiveSample("%E7%A7%BB%E5%8A%A8%E7%AC%94%E8%A7%A6", 160, 160, "https://mdn.mozillademos.org/files/252/Canvas_smiley.png")}}</p> + +<p>如果你想看到连续的线,你可以移除调用的moveTo()。</p> + +<div class="note"> +<p><strong>注意:需要学习更多关于arc()函数的内容,请看下面的</strong>{{anch("圆弧")}}</p> +</div> + +<h3 id="线">线</h3> + +<p>绘制直线,需要用到的方法<code>lineTo()</code>。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.lineTo", "lineTo(x, y)")}}</dt> + <dd>绘制一条从当前位置到指定x以及y位置的直线。</dd> +</dl> + +<p>该方法有两个参数:x以及y ,代表坐标系中直线结束的点。开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,等等。。。开始点也可以通过<code>moveTo()</code>函数改变。</p> + +<p>下面的例子绘制两个三角形,一个是填充的,另一个是描边的。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">function draw() { + var canvas = document.getElementById('canvas'); + if (canvas.getContext){ + var ctx = canvas.getContext('2d'); + + // 填充三角形 + ctx.beginPath(); + ctx.moveTo(25, 25); + ctx.lineTo(105, 25); + ctx.lineTo(25, 105); + ctx.fill(); + + // 描边三角形 + ctx.beginPath(); + ctx.moveTo(125, 125); + ctx.lineTo(125, 45); + ctx.lineTo(45, 125); + ctx.closePath(); + ctx.stroke(); + } +}</pre> + +<p>这里从调用<code>beginPath()</code>函数准备绘制一个新的形状路径开始。然后使用<code>moveTo()</code>函数移动到目标位置上。然后下面,两条线段绘制后构成三角形的两条边。</p> + +<p>{{EmbedLiveSample("%E7%BA%BF", 160, 160, "https://mdn.mozillademos.org/files/238/Canvas_lineTo.png")}}</p> + +<p>你会注意到填充与描边三角形步骤有所不同。正如上面所提到的,因为路径使用填充(fill)时,路径自动闭合,使用描边(stroke)则不会闭合路径。如果没有添加闭合路径<code>closePath()</code>到描述三角形函数中,则只绘制了两条线段,并不是一个完整的三角形。</p> + +<h3 id="圆弧">圆弧</h3> + +<p>绘制圆弧或者圆,我们使用<code>arc()</code>方法。当然可以使用<code>arcTo()</code>,不过这个的实现并不是那么的可靠,所以我们这里不作介绍。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.arc", "arc(x, y, radius, startAngle, endAngle, anticlockwise)")}}</dt> + <dd>画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。</dd> + <dt>{{domxref("CanvasRenderingContext2D.arcTo", "arcTo(x1, y1, x2, y2, radius)")}}</dt> + <dd>根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。</dd> +</dl> + +<p>这里详细介绍一下arc方法,该方法有六个参数:<code>x,y</code>为绘制圆弧所在圆上的圆心坐标。<font><code>radius</code>为半径。<code>startAngle</code>以及<code>endAngle</code>参数用弧度定义了开始以及结束的弧度。这些都是以x轴为基准。参数</font><code style="font-style: normal; line-height: 1.5;">anticlockwise</code>为一个布尔值。为true时,是逆时针方向,否则顺时针方向。</p> + +<div class="note"> +<p><strong>注意:<code>arc()</code>函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式:</strong></p> + +<p><strong>弧度=(Math.PI/180)*角度。</strong></p> +</div> + +<p>下面的例子比上面的要复杂一下,下面绘制了12个不同的角度以及填充的圆弧。</p> + +<p>下面两个<code>for</code>循环,生成圆弧的行列(x,y)坐标。每一段圆弧的开始都调用<code>beginPath()</code>。代码中,每个圆弧的参数都是可变的,实际编程中,我们并不需要这样做。</p> + +<p>x,y坐标是可变的。半径(radius)和开始角度(startAngle)都是固定的。结束角度(endAngle)在第一列开始时是180度(半圆)然后每列增加90度。最后一列形成一个完整的圆。</p> + +<p><code style="font-style: normal; line-height: 1.5;">clockwise</code>语句作用于第一、三行是顺时针的圆弧,<code>anticlockwise</code>作用于二、四行为逆时针圆弧。<code>if</code>语句让一、二行描边圆弧,下面两行填充路径。</p> + +<div class="note"> +<p><strong>注意:</strong> 这个示例所需的画布大小略大于本页面的其他例子: 150 x 200 像素。</p> +</div> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="200"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">function draw() { + var canvas = document.getElementById('canvas'); + if (canvas.getContext){ + var ctx = canvas.getContext('2d'); + + for(var i = 0; i < 4; i++){ + for(var j = 0; j < 3; j++){ + ctx.beginPath(); + var x = 25 + j * 50; // x 坐标值 + var y = 25 + i * 50; // y 坐标值 + var radius = 20; // 圆弧半径 + var startAngle = 0; // 开始点 + var endAngle = Math.PI + (Math.PI * j) / 2; // 结束点 + var anticlockwise = i % 2 == 0 ? false : true; // 顺时针或逆时针 + + ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise); + + if (i>1){ + ctx.fill(); + } else { + ctx.stroke(); + } + } + } + } +} +</pre> + +<p>{{EmbedLiveSample("%E5%9C%86%E5%BC%A7", 160, 210, "https://mdn.mozillademos.org/files/204/Canvas_arc.png")}}</p> + +<h3 id="二次贝塞尔曲线及三次贝塞尔曲线">二次贝塞尔曲线及三次贝塞尔曲线</h3> + +<p>下一个十分有用的路径类型就是<a href="https://zh.wikipedia.org/wiki/%E8%B2%9D%E8%8C%B2%E6%9B%B2%E7%B7%9A" rel="external">贝塞尔曲线</a>。二次及三次贝塞尔曲线都十分有用,一般用来绘制复杂有规律的图形。</p> + +<dl> + <dt><code>quadraticCurveTo(cp1x, cp1y, x, y)</code></dt> + <dd>绘制二次贝塞尔曲线,<code>cp1x,cp1y</code>为一个控制点,<code>x,y为</code>结束点。</dd> + <dt><code>bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)</code></dt> + <dd>绘制三次贝塞尔曲线,<code>cp1x,cp1y</code>为控制点一,<code>cp2x,cp2y</code>为控制点二,<code>x,y</code>为结束点。</dd> +</dl> + +<p><img alt="" src="https://mdn.mozillademos.org/files/223/Canvas_curves.png" style="float: right; height: 190px; width: 190px;">右边的图能够很好的描述两者的关系,二次贝塞尔曲线有一个开始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点。</p> + +<p>参数x、y在这两个方法中都是结束点坐标。<code>cp1x,cp1y</code>为坐标中的第一个控制点,<code>cp2x,cp2y</code>为坐标中的第二个控制点。</p> + +<p>使用二次以及三次贝塞尔曲线是有一定的难度的,因为不同于像Adobe Illustrators这样的矢量软件,我们所绘制的曲线没有给我们提供直接的视觉反馈。这让绘制复杂的图形变得十分困难。在下面的例子中,我们会绘制一些简单有规律的图形,如果你有时间以及更多的耐心,很多复杂的图形你也可以绘制出来。</p> + +<p>下面的这些例子没有多少困难。这两个例子中我们会连续绘制贝塞尔曲线,最后形成复杂的图形。</p> + +<h4 id="二次贝塞尔曲线">二次贝塞尔曲线</h4> + +<p>这个例子使用多个贝塞尔曲线来渲染对话气泡。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">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(); + } +} +</pre> + +<p>{{EmbedLiveSample("%E4%BA%8C%E6%AC%A1%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF", 160, 160, "https://mdn.mozillademos.org/files/243/Canvas_quadratic.png")}}</p> + +<h4 id="三次贝塞尔曲线">三次贝塞尔曲线</h4> + +<p>这个例子使用贝塞尔曲线绘制心形。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js notranslate">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(); + } +} +</pre> + +<p>{{EmbedLiveSample("%E4%B8%89%E6%AC%A1%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF", 160, 160, "https://mdn.mozillademos.org/files/207/Canvas_bezier.png")}}</p> + +<h3 id="矩形">矩形</h3> + +<p>直接在画布上绘制矩形的三个额外方法,正如我们开始所见的{{anch("绘制矩形")}},同样,也有rect()方法,将一个矩形路径增加到当前路径上。</p> + +<dl> + <dt><code>rect(<em>x</em>, <em>y</em>, <em>width</em>, <em>height</em>)</code></dt> + <dd>绘制一个左上角坐标为(x,y),宽高为width以及height的矩形。</dd> +</dl> + +<p>当该方法执行的时候,moveTo()方法自动设置坐标参数(0,0)。也就是说,当前笔触自动重置回默认坐标。</p> + +<h3 id="组合使用">组合使用</h3> + +<p>目前为止,每一个例子中的每个图形都只用到一种类型的路径。然而,绘制一个图形并没有限制使用数量以及类型。所以在最后的一个例子里,让我们组合使用所有的路径函数来重现一款著名的游戏。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html></pre> +</div> + +<pre class="brush: js notranslate">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(); +} +</pre> + +<p>结果画面如下:</p> + +<p>{{EmbedLiveSample("%E7%BB%84%E5%90%88%E4%BD%BF%E7%94%A8", 160, 160, "https://mdn.mozillademos.org/files/9849/combinations.png")}}</p> + +<p>我们不会很详细地讲解上面的代码,因为事实上这很容易理解。重点是绘制上下文中使用到了fillStyle属性,以及封装函数(例子中的<code>roundedRect()</code>)。使用封装函数对于减少代码量以及复杂度十分有用。</p> + +<p>在稍后的课程里,我们会讨论<code>fillStyle</code>样式的更多细节。这章节中,我们对<code>fillStyle</code>样式所做的仅是改变填充颜色,由默认的黑色到白色,然后又是黑色。</p> + +<h2 id="Path2D_对象">Path2D 对象</h2> + +<p>正如我们在前面例子中看到的,你可以使用一系列的路径和绘画命令来把对象“画”在画布上。为了简化代码和提高性能,{{domxref("Path2D")}}对象已可以在较新版本的浏览器中使用,用来缓存或记录绘画命令,这样你将能快速地回顾路径。</p> + +<p>怎样产生一个Path2D对象呢?</p> + +<dl> + <dt>{{domxref("Path2D.Path2D", "Path2D()")}}</dt> + <dd><code>Path2D()</code>会返回一个新初始化的Path2D对象(可能将某一个路径作为变量——创建一个它的副本,或者将一个包含SVG path数据的字符串作为变量)。</dd> +</dl> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js">new Path2D(); // 空的Path对象 +new Path2D(path); // 克隆Path对象 +new Path2D(d); // 从SVG建立Path对象</code></pre> + +<p>所有的路径方法比如<code>moveTo</code>, <code>rect</code>, <code>arc</code>或<code>quadraticCurveTo</code>等,如我们前面见过的,都可以在Path2D中使用。</p> + +<p>Path2D API 添加了 <code>addPath</code>作为将<code>path</code>结合起来的方法。当你想要从几个元素中来创建对象时,这将会很实用。比如:</p> + +<dl> + <dt><strong>{{domxref("Path2D.addPath", "Path2D.addPath(path [, transform])")}}</strong></dt> + <dd>添加了一条路径到当前路径(可能添加了一个变换矩阵)。</dd> +</dl> + +<h3 id="Path2D_示例">Path2D 示例</h3> + +<p>在这个例子中,我们创造了一个矩形和一个圆。它们都被存为Path2D对象,后面再派上用场。随着新的Path2D API产生,几种方法也相应地被更新来使用Path2D对象而不是当前路径。在这里,带路径参数的<code>stroke</code>和<code>fill</code>可以把对象画在画布上。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="130" height="100"></canvas> + </body> +</html> +</pre> +</div> + +<pre class="brush: js;highlight[6,9] line-numbers language-js notranslate"><code class="language-js">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); + } +}</code></pre> + +<p>{{EmbedLiveSample("Path2D_%E7%A4%BA%E4%BE%8B", 130, 110, "https://mdn.mozillademos.org/files/9851/path2d.png")}}</p> + +<h3 id="使用_SVG_paths">使用 SVG paths</h3> + +<p>新的Path2D API有另一个强大的特点,就是使用SVG path data来初始化canvas上的路径。这将使你获取路径时可以以SVG或canvas的方式来重用它们。</p> + +<p>这条路径将先移动到点 <code>(M10 10)</code> 然后再水平移动80个单位<code>(h 80)</code>,然后下移80个单位 <code>(v 80)</code>,接着左移80个单位 <code>(h -80)</code>,再回到起点处 (<code>z</code>)。你可以在<a href="https://developer.mozilla.org/en-US/docs/Web/API/Path2D.Path2D#Using_SVG_paths">Path2D constructor</a> 查看这个例子。</p> + +<pre class="brush: js; line-numbers language-js notranslate"><code class="language-js">var p = new Path2D("M10 10 h 80 v 80 h -80 Z");</code></pre> +</div> + +<div>{{PreviousNext("Web/API/Canvas_API/Tutorial/Basic_usage", "Web/API/Canvas_API/Tutorial/Applying_styles_and_colors")}}</div> +</div> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/drawing_text/index.html b/files/zh-cn/web/api/canvas_api/tutorial/drawing_text/index.html new file mode 100644 index 0000000000..196b8594ea --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/drawing_text/index.html @@ -0,0 +1,162 @@ +--- +title: 绘制文本 +slug: Web/API/Canvas_API/Tutorial/Drawing_text +translation_of: Web/API/Canvas_API/Tutorial/Drawing_text +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Applying_styles_and_colors", "Web/API/Canvas_API/Tutorial/Using_images")}}</div> + +<div class="summary"> +<p>在前一个章节中看过 <a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors">应用样式和颜色</a> 之后, 我们现在来看一下如何在canvas中绘制文本</p> +</div> + +<h2 id="绘制文本">绘制文本</h2> + +<p>canvas 提供了两种方法来渲染文本:</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.fillText", "fillText(text, x, y [, maxWidth])")}}</dt> + <dd>在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.</dd> + <dt>{{domxref("CanvasRenderingContext2D.strokeText", "strokeText(text, x, y [, maxWidth])")}}</dt> + <dd>在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.</dd> +</dl> + +<h3 id="A_fillText_example" name="A_fillText_example">一个填充文本的示例</h3> + +<p>文本用当前的填充方式被填充:</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + ctx.font = "48px serif"; + ctx.fillText("Hello world", 10, 50); +}</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="300" height="100"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_fillText_example", 310, 110)}}</p> + +<h3 id="A_strokeText_example" name="A_strokeText_example">一个文本边框的示例</h3> + +<p>文本用当前的边框样式被绘制:</p> + +<pre class="brush: js;highlight[4] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + ctx.font = "48px serif"; + ctx.strokeText("Hello world", 10, 50); +}</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="300" height="100"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_strokeText_example", 310, 110)}}</p> + +<h2 id="有样式的文本">有样式的文本</h2> + +<p>在上面的例子用我们已经使用了 <code>font</code> 来使文本比默认尺寸大一些. 还有更多的属性可以让你改变canvas显示文本的方式:</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.font", "font = value")}}</dt> + <dd>当前我们用来绘制文本的样式. 这个字符串使用和 <a href="/en-US/docs/Web/CSS">CSS</a> {{cssxref("font")}} 属性相同的语法. 默认的字体是 <code>10px sans-serif</code>。</dd> + <dt>{{domxref("CanvasRenderingContext2D.textAlign", "textAlign = value")}}</dt> + <dd>文本对齐选项. 可选的值包括:<code>start</code>, <code>end</code>, <code>left</code>, <code>right</code> or <code>center</code>. 默认值是 <code>start</code>。</dd> + <dt>{{domxref("CanvasRenderingContext2D.textBaseline", "textBaseline = value")}}</dt> + <dd>基线对齐选项. 可选的值包括:<code>top</code>, <code>hanging</code>, <code>middle</code>, <code>alphabetic</code>, <code>ideographic</code>, <code>bottom</code>。默认值是 <code>alphabetic。</code></dd> + <dt>{{domxref("CanvasRenderingContext2D.direction", "direction = value")}}</dt> + <dd>文本方向。可能的值包括:<code>ltr</code>, <code>rtl</code>, <code>inherit</code>。默认值是 <code>inherit<font face="Thread-00001740-Id-000000e0">。</font></code></dd> +</dl> + +<p>如果你之前使用过CSS,那么这些选项你会很熟悉。</p> + +<p>下面的图片(from the <a class="external" href="http://www.whatwg.org/" title="http://www.whatwg.org/">WHATWG</a>)展示了textBaseline属性支持的不同的基线情况:</p> + +<p><img alt="The top of the em square is +roughly at the top of the glyphs in a font, the hanging baseline is +where some glyphs like आ are anchored, the middle is half-way +between the top of the em square and the bottom of the em square, +the alphabetic baseline is where characters like Á, ÿ, +f, and Ω are anchored, the ideographic baseline is +where glyphs like 私 and 達 are anchored, and the bottom +of the em square is roughly at the bottom of the glyphs in a +font. The top and bottom of the bounding box can be far from these +baselines, due to glyphs extending far outside the em square." src="http://www.whatwg.org/specs/web-apps/current-work/images/baselines.png"></p> + +<h3 id="textBaseline例子">textBaseline例子</h3> + +<p>编辑下面的代码,看看它们在canvas中的变化:</p> + +<pre class="brush: js;highlight[2] notranslate">ctx.font = "48px serif"; +ctx.textBaseline = "hanging"; +ctx.strokeText("Hello world", 0, 100); +</pre> + +<div class="hidden"> +<h6 id="Playable_code" name="Playable_code">Playable code</h6> + +<pre class="brush: html notranslate"><canvas id="canvas" width="400" height="200" class="playable-canvas"></canvas> +<div class="playable-buttons"> + <input id="edit" type="button" value="Edit" /> + <input id="reset" type="button" value="Reset" /> +</div> +<textarea id="code" class="playable-code"> +ctx.font = "48px serif"; +ctx.textBaseline = "hanging"; +ctx.strokeText("Hello world", 0, 100);</textarea> +</pre> + +<pre class="brush: js notranslate">var canvas = document.getElementById("canvas"); +var ctx = canvas.getContext("2d"); +var textarea = document.getElementById("code"); +var reset = document.getElementById("reset"); +var edit = document.getElementById("edit"); +var code = textarea.value; + +function drawCanvas() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + eval(textarea.value); +} + +reset.addEventListener("click", function() { + textarea.value = code; + drawCanvas(); +}); + +edit.addEventListener("click", function() { + textarea.focus(); +}) + +textarea.addEventListener("input", drawCanvas); +window.addEventListener("load", drawCanvas); +</pre> +</div> + +<p>{{ EmbedLiveSample('Playable_code', 700, 360) }}</p> + +<h2 id="预测量文本宽度">预测量文本宽度</h2> + +<p>当你需要获得更多的文本细节时,下面的方法可以给你测量文本的方法。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.measureText", "measureText()")}}</dt> + <dd>将返回一个 {{domxref("TextMetrics")}}对象的宽度、所在像素,这些体现文本特性的属性。</dd> +</dl> + +<p>下面的代码段将展示如何测量文本来获得它的宽度:</p> + +<pre class="brush: js;highlight[3] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + var text = ctx.measureText("foo"); // TextMetrics object + text.width; // 16; +} +</pre> + +<h2 id="Geoko特性说明">Geoko特性说明</h2> + +<p>在Geoko(Firefox,Firefox OS及基于Mozilla的应用的渲染引擎)中,曾有一些版本较早的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D#Prefixed_APIs">API</a> 实现了在canvas上对文本作画的功能,但它们现在已不再使用。</p> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Applying_styles_and_colors", "Web/API/Canvas_API/Tutorial/Using_images")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/finale/index.html b/files/zh-cn/web/api/canvas_api/tutorial/finale/index.html new file mode 100644 index 0000000000..8256751a35 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/finale/index.html @@ -0,0 +1,68 @@ +--- +title: 终曲 +slug: Web/API/Canvas_API/Tutorial/Finale +tags: + - 终曲 +translation_of: Web/API/Canvas_API/Tutorial/Finale +--- +<div> </div> + +<div class="summary"> +<p>恭喜你!你已经结束了Canvas的教程!这些知识将帮助你在web中创建优秀的2D图形。</p> +</div> + +<h2 id="更多的例子以及教程">更多的例子以及教程</h2> + +<p>下面这些网站里有关于canvas的各种各样的演示以及更进一步的说明:</p> + +<dl> + <dt><a href="http://codepen.io/search?q=canvas">Codepen.io</a></dt> + <dd>前端开发人员操练场和浏览器内的代码编辑器。</dd> + <dt><a href="http://www.canvasdemos.com/">Canvasdemos.com</a>(域名已失效)</dt> + <dd>关于HTML5 canvas元素的应用程序,游戏,工具和教程。</dd> + <dt><a href="http://www.html5canvastutorials.com/">HTML5CanvasTutorials</a></dt> + <dd>包括大多数canvas API的示例。</dd> + <dt><a href="http://creativejs.com/2011/08/31-days-of-canvas-tutorials/">31 days of Canvas tutorials</a></dt> + <dd>JavaScript可视化编程入门的不错选择。</dd> + <dt><a href="/en-US/docs/Games">Game development</a></dt> + <dd>玩游戏是最热门的计算机活动之一。新技术不断诞生,以使开发更好更强、且能在任何符合标准的浏览器上运行的游戏变得可能。</dd> +</dl> + +<h2 id="其他的Web_APIs">其他的Web APIs</h2> + +<p>当和canvas和图形进一步打交道时,这些API可能会排上用场。</p> + +<dl> + <dt><a href="/en-US/docs/Web/WebGL">WebGL</a></dt> + <dd>用于渲染交互式3D图形的API.</dd> + <dt><a href="/en-US/docs/Web/SVG">SVG</a></dt> + <dd>可缩放矢量图形(简称SVG)允许你使用矢量线,矢量图形,确保无论图片大小都可以比例平滑地显示.</dd> + <dt><a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio</a></dt> + <dd> + <div class="trans-input-wrap"> + <div class="input-wrap" dir="ltr" style="height: auto;"> + <div class="textarea-wrap" style="height: 88px; padding-bottom: 0px;"> + <div class="textarea-bg-text small-font" id="textarea-bg-text"> + <div class="output-wrap small-font"> + <div class="output-mod ordinary-wrap"> + <div class="output-bd" dir="ltr" style="padding-bottom: 6px;"> + <p class="ordinary-output target-output clearfix"><span>Web Audio API提供了Web上的音频控制——个强大和灵活的系统,允许开发人员选择的音频源,添加效果的音频,创建音频可视化,应用空间效应(如平移)等等。</span></p> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </dd> + <dd> </dd> +</dl> + +<h2 id="解惑">解惑</h2> + +<dl> + <dt><a href="http://stackoverflow.com/questions/tagged/canvas">Stackoverflow</a></dt> + <dd>到"canvas"标签来提问</dd> + <dt><a href="/en-US/docs/MDN">Comments about this tutorial – the MDN documentation community</a></dt> + <dd>如果你对本教程有任何意见,或是想感谢我们,在此畅所欲言吧!</dd> +</dl> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/hit_regions_and_accessibility/index.html b/files/zh-cn/web/api/canvas_api/tutorial/hit_regions_and_accessibility/index.html new file mode 100644 index 0000000000..1e4a70f289 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/hit_regions_and_accessibility/index.html @@ -0,0 +1,97 @@ +--- +title: 点击区域和无障碍访问 +slug: Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility +translation_of: Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility +--- +<div>{{CanvasSidebar}} {{ PreviousNext("Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas", "Web/API/Canvas_API/Tutorial/Optimizing_canvas") }}</div> + +<div class="summary">{{HTMLElement("canvas")}} 标签只是一个位图,它并不提供任何已经绘制在上面的对象的信息。 canvas的内容不能像语义化的HTML一样暴露给一些协助工具。一般来说,你应该避免在交互型的网站或者App上使用canvas。接下来的内容能帮助你让canvas更加容易交互。</div> + +<h2 id="内容兼容">内容兼容</h2> + +<p><canvas> ... </canvas>标签里的内容被可以对一些不支持canvas的浏览器提供兼容。这对残疾用户设备也很有用(比如屏幕阅读器),这样它们就可以读取并解释DOM里的子节点。在<a href="http://www.html5accessibility.com/tests/canvas.html">html5accessibility.com</a>有个很好的例子来演示它如何运作。</p> + +<pre class="brush: html"><canvas> + <h2>Shapes</h2> + <p>A rectangle with a black border. + In the background is a pink circle. + Partially overlaying the <a href="http://en.wikipedia.org/wiki/Circle" onfocus="drawCircle();" onblur="drawPicture();">circle</a>. + Partially overlaying the circle is a green + <a href="http://en.wikipedia.org/wiki/Square" onfocus="drawSquare();" onblur="drawPicture();">square</a> + and a purple <a href="http://en.wikipedia.org/wiki/Triangle" onfocus="drawTriangle();" onblur="drawPicture();">triangle</a>, + both of which are semi-opaque, so the full circle can be seen underneath.</p> +</canvas> </pre> + +<p>演示视频:<a href="https://www.youtube.com/watch?v=ABeIFlqYiMQ">video how NVDA reads this example by Steve Faulkner</a>.</p> + +<h2 id="ARIA_规则">ARIA 规则</h2> + +<p>Accessible Rich Internet Applications <strong>(<a href="/en-US/docs/Web/Accessibility/ARIA">ARIA</a>)</strong> 定义了让Web内容和Web应用更容易被有身体缺陷的人获取的办法。你可以用ARIA属性来描述canvas元素的行为和存在目的。详情见<a href="/en-US/docs/Web/Accessibility/ARIA">ARIA</a>和 <a href="/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques">ARIA 技术</a>。</p> + +<pre class="brush: html"><canvas id="button" tabindex="0" role="button" aria-pressed="false" aria-label="Start game"></canvas> +</pre> + +<h2 id="点击区域(hit_region)">点击区域(hit region)</h2> + +<p>判断鼠标坐标是否在canvas上一个特定区域里一直是个有待解决的问题。hit region API让你可以在canvas上定义一个区域,这让无障碍工具获取canvas上的交互内容成为可能。它能让你更容易地进行点击检测并把事件转发到DOM元素去。这个API有以下三个方法(都是实验性特性,请先在浏览器兼容表上确认再使用)。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.addHitRegion()")}} {{experimental_inline}}</dt> + <dd>在canvas上添加一个点击区域。</dd> + <dt>{{domxref("CanvasRenderingContext2D.removeHitRegion()")}} {{experimental_inline}}</dt> + <dd>从canvas上移除指定id的点击区域。</dd> + <dt>{{domxref("CanvasRenderingContext2D.clearHitRegions()")}} {{experimental_inline}}</dt> + <dd>移除canvas上的所有点击区域。</dd> +</dl> + +<p>你可以把一个点击区域添加到路径里并检测{{domxref("MouseEvent.region")}}属性来测试你的鼠标有没有点击这个区域,例:</p> + +<pre class="brush: html"><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></pre> + +<p><code>addHitRegion()方法也可以带一个control选项来指定把事件转发到哪个元素上(canvas里的元素)。</code></p> + +<pre class="brush: js">ctx.addHitRegion({control: element});</pre> + +<p>把这个特性用在{{HTMLElement("input")}}元素上会很有用。 看看这个例子:<a href="http://codepen.io/peterj35/pen/PEdLKx">codepen demo</a>.</p> + +<h2 id="焦点圈">焦点圈</h2> + +<p>当用键盘控制时,焦点圈是一个能帮我们在页面上快速导航的标记。要在canvas上绘制焦点圈,可以使用<code>drawFocusIfNeeded</code> 属性。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.drawFocusIfNeeded()")}} {{experimental_inline}}</dt> + <dd>如果给定的元素获得了焦点,这个方法会沿着在当前的路径画个焦点圈。</dd> +</dl> + +<p>另外,<code>scrollPathIntoView()方法可以让一个元素获得焦点的时候在屏幕上可见(滚动到元素所在的区域)</code>。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.scrollPathIntoView()")}} {{experimental_inline}}</dt> + <dd>把当前的路径或者一个给定的路径滚动到显示区域内。</dd> +</dl> + +<h2 id="相关内容">相关内容</h2> + +<ul> + <li><a href="https://www.w3.org/WAI/PF/HTML/wiki/Canvas_Accessibility_Use_Cases">Canvas accessibility use cases</a></li> + <li><a href="https://www.w3.org/html/wg/wiki/AddedElementCanvas">Canvas element accessibility issues</a></li> + <li><a href="http://www.paciellogroup.com/blog/2012/06/html5-canvas-accessibility-in-firefox-13/">HTML5 Canvas Accessibility in Firefox 13 – by Steve Faulkner</a></li> + <li><a href="https://html.spec.whatwg.org/multipage/scripting.html#best-practices">Best practices for interactive canvas elements</a></li> +</ul> + +<div>{{ PreviousNext("Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas", "Web/API/Canvas_API/Tutorial/Optimizing_canvas") }}</div> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/index.html b/files/zh-cn/web/api/canvas_api/tutorial/index.html new file mode 100644 index 0000000000..454cb843ba --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/index.html @@ -0,0 +1,58 @@ +--- +title: Canvas教程 +slug: Web/API/Canvas_API/Tutorial +tags: + - Canvas + - HTML + - HTML5 + - Web + - 中级 + - 图像 + - 教程 + - 画布 + - 进阶 +translation_of: Web/API/Canvas_API/Tutorial +--- +<div>{{CanvasSidebar}}</div> + +<p><a href="/zh-CN/docs/HTML/Canvas" title="HTML/Canvas"><img alt="" src="https://mdn.mozillademos.org/files/257/Canvas_tut_examples.jpg" style="float: right; height: 315px; width: 140px;"></a></p> + +<div class="summary"> +<p><a href="/zh-CN/docs/Web/HTML/Element/canvas" title="HTML/Canvas"><strong><code><canvas></code></strong></a>是一个可以使用脚本(通常为<a href="/zh-CN/docs/JavaScript" title="zh-CN/docs/JavaScript">JavaScript</a>)来绘制图形的 <a href="/zh-CN/docs/HTML" title="zh-CN/docs/HTML">HTML</a> 元素.例如,它可以用于绘制图表、制作图片构图或者制作简单的(以及<a href="/zh-CN/docs/Web/API/Canvas_API/A_basic_ray-caster">不那么简单的</a>)动画. 右边的图片展示了一些 <code><canvas></code> 的实现示例,在这个教程后面我们将看到.</p> +</div> + +<p>本篇教程从一些基础开始,描述了如何使用<canvas>元素来绘制2D图形。教程中提供的例子,会让你明白可以用canvas做什么,也会提供一些代码片段来帮助你开始构建自己的内容。</p> + +<p><code><canvas></code> 最早由Apple引入WebKit,用于Mac OS X 的 Dashboard,随后被各个浏览器实现。如今,所有主流的浏览器都支持它。</p> + +<h2 id="Before_you_start" name="Before_you_start">开始之前</h2> + +<p>使用 <code><canvas></code> 元素不是非常难,但你需要一些基本的<a href="/zh-CN/docs/HTML" title="zh-CN/docs/HTML">HTML</a>和<a href="/zh-CN/docs/JavaScript" title="zh-CN/docs/JavaScript">JavaScript</a>知识。除一些过时的浏览器不支持<code><canvas></code> 元素外,所有的新版本主流浏览器都支持它。Canvas 的默认大小为300像素×150像素(宽×高,像素的单位是px)。但是,可以使用HTML的高度和宽度属性来自定义Canvas 的尺寸。为了在 Canvas 上绘制图形,我们使用一个JavaScript上下文对象,它能动态创建图像( creates graphics on the fly)。</p> + +<h2 id="In_this_tutorial" name="In_this_tutorial">本教程包含</h2> + +<ul> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_usage" title="zh-CN/docs/Canvas_tutorial/Basic_usage">基本用法</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes" title="zh-CN/docs/Canvas_tutorial/Drawing_shapes">绘制图形</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors">使用样式与颜色</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text">绘制文本</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Using_images">使用图像</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Transformations">变形</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing" title="Canvas_tutorial/Compositing">合成和剪辑</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Basic_animations" title="zh-CN/docs/Canvas_tutorial/Basic_animations">基本动画</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Advanced_animations">高级动画</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas">像素处理</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility">点击区域和无障碍访问</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas" title="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Optimizing_canvas">优化 canvas</a></li> + <li><a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Finale">终曲</a></li> +</ul> + +<h2 id="See_also" name="See_also">相关链接</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" title="HTML/Canvas">Canvas 专题页</a></li> + <li><a href="http://visitmix.com/labs/ai2canvas/" title="http://visitmix.com/labs/ai2canvas/">Adobe Illustrator to Canvas plug-in</a></li> + <li><a href="http://www.html5canvastutorials.com/" title="http://www.html5canvastutorials.com/">HTML5 Canvas教程</a></li> +</ul> + +<p>{{ Next("Web/API/Canvas_API/Tutorial/Basic_usage") }}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/optimizing_canvas/index.html b/files/zh-cn/web/api/canvas_api/tutorial/optimizing_canvas/index.html new file mode 100644 index 0000000000..cfa0a7250f --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/optimizing_canvas/index.html @@ -0,0 +1,115 @@ +--- +title: canvas的优化 +slug: Web/API/Canvas_API/Tutorial/Optimizing_canvas +tags: + - Advanced + - Canvas + - Tutorial +translation_of: Web/API/Canvas_API/Tutorial/Optimizing_canvas +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility", "Web/API/Canvas_API/Tutorial/Finale")}}</div> + +<div> +<p class="summary">{{HTMLElement("canvas")}}元素是众多广泛使用的网络2D图像渲染标准之一。它被广泛用于游戏及复杂的图像可视化中。然而,随着网站和应用将canvas画布推至极限,性能开始成为问题。此文目标是给使用canvas画布元素的优化带来建议,去保证你的网站或者应用表现卓越。</p> +</div> + +<h2 id="性能贴士">性能贴士</h2> + +<p>下面是一些改善性能的建议</p> + +<h3 id="在离屏canvas上预渲染相似的图形或重复的对象">在离屏canvas上预渲染相似的图形或重复的对象</h3> + +<p>如果发现自己在每个动画帧上重复了一些相同的绘制操作,请考虑将其分流到屏幕外的画布上。 然后,您可以根据需要频繁地将屏幕外图像渲染到主画布上,而不必首先重复生成该图像的步骤。</p> + +<pre class="brush: js">myEntity.offscreenCanvas = document.createElement("canvas"); +myEntity.offscreenCanvas.width = myEntity.width; +myEntity.offscreenCanvas.height = myEntity.height; +myEntity.offscreenContext = myEntity.offscreenCanvas.getContext("2d"); + +myEntity.render(myEntity.offscreenContext); +</pre> + +<h3 id="避免浮点数的坐标点,用整数取而代之">避免浮点数的坐标点,用整数取而代之</h3> + +<p>当你画一个没有整数坐标点的对象时会发生子像素渲染。</p> + +<pre class="brush: js">ctx.drawImage(myImage, 0.3, 0.5); +</pre> + +<p>浏览器为了达到抗锯齿的效果会做额外的运算。为了避免这种情况,请保证在你调用{{domxref("CanvasRenderingContext2D.drawImage", "drawImage()")}}函数时,用{{jsxref("Math.floor()")}}函数对所有的坐标点取整。</p> + +<h3 id="不要在用drawImage时缩放图像">不要在用<code>drawImage</code>时缩放图像</h3> + +<p>在离屏canvas中缓存图片的不同尺寸,而不要用{{domxref("CanvasRenderingContext2D.drawImage", "drawImage()")}}去缩放它们。</p> + +<h3 id="使用多层画布去画一个复杂的场景">使用多层画布去画一个复杂的场景</h3> + +<p>在您的应用程序中,您可能会发现某些对象需要经常移动或更改,而其他对象则保持相对静态。 在这种情况下,可能的优化是使用多个<code><canvas></code>元素对您的项目进行分层。</p> + +<p>例如,假设您有一个游戏,其UI位于顶部,中间是游戏性动作,底部是静态背景。 在这种情况下,您可以将游戏分成三个<code><canvas></code>层。 UI将仅在用户输入时发生变化,游戏层随每个新框架发生变化,并且背景通常保持不变。</p> + +<pre class="brush: html"><div id="stage"> + <canvas id="ui-layer" width="480" height="320"></canvas> + <canvas id="game-layer" width="480" height="320"></canvas> + <canvas id="background-layer" width="480" height="320"></canvas> +</div> + +<style> + #stage { + width: 480px; + height: 320px; + position: relative; + border: 2px solid black + } + canvas { position: absolute; } + #ui-layer { z-index: 3 } + #game-layer { z-index: 2 } + #background-layer { z-index: 1 } +</style> +</pre> + +<h3 id="用CSS设置大的背景图">用CSS设置大的背景图</h3> + +<p>如果像大多数游戏那样,你有一张静态的背景图,用一个静态的{{HTMLElement("div")}}元素,结合{{cssxref("background")}} 特性,以及将它置于画布元素之后。这么做可以避免在每一帧在画布上绘制大图。</p> + +<h3 id="用CSS_transforms特性缩放画布">用CSS transforms特性缩放画布</h3> + +<p><a href="/en-US/docs/Web/Guide/CSS/Using_CSS_transforms">CSS transforms</a> 使用GPU,因此速度更快。 最好的情况是不直接缩放画布,或者具有较小的画布并按比例放大,而不是较大的画布并按比例缩小。</p> + +<pre class="brush: js">var scaleX = window.innerWidth / canvas.width; +var scaleY = window.innerHeight / canvas.height; + +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 + ')'; +</pre> + +<h3 id="关闭透明度">关闭透明度</h3> + +<p>如果你的游戏使用画布而且不需要透明,当使用 <code><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext" title="The HTMLCanvasElement.getContext() method returns a drawing context on the canvas, or null if the context identifier is not supported.">HTMLCanvasElement.getContext()</a></code> 创建一个绘图上下文时把 <code>alpha</code> 选项设置为 <code>false</code> 。这个选项可以帮助浏览器进行内部优化。</p> + +<pre class="brush: js">var ctx = canvas.getContext('2d', { alpha: false });</pre> + +<h3 id="更多的贴士">更多的贴士</h3> + +<ul> + <li>将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)</li> + <li>避免不必要的画布状态改变</li> + <li>渲染画布中的不同点,而非整个新状态</li> + <li>尽可能避免 {{domxref("CanvasRenderingContext2D.shadowBlur", "shadowBlur")}}特性</li> + <li>尽可能避免<a href="/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text">text rendering</a></li> + <li>尝试不同的方法来清除画布({{domxref("CanvasRenderingContext2D.clearRect", "clearRect()")}} vs. {{domxref("CanvasRenderingContext2D.fillRect", "fillRect()")}} vs. 调整canvas大小)</li> + <li> 有动画,请使用{{domxref("window.requestAnimationFrame()")}} 而非{{domxref("window.setInterval()")}}</li> + <li>请谨慎使用大型物理库</li> +</ul> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="http://www.html5rocks.com/en/tutorials/canvas/performance/#toc-ref">Improving HTML5 Canvas Performance – HTML5 Rocks</a></li> + <li><a href="https://hacks.mozilla.org/2013/05/optimizing-your-javascript-game-for-firefox-os/">Optimizing your JavaScript game for Firefox OS – Mozilla Hacks</a></li> +</ul> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility", "Web/API/Canvas_API/Tutorial/Finale")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/pixel_manipulation_with_canvas/index.html b/files/zh-cn/web/api/canvas_api/tutorial/pixel_manipulation_with_canvas/index.html new file mode 100644 index 0000000000..268d375903 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/pixel_manipulation_with_canvas/index.html @@ -0,0 +1,356 @@ +--- +title: 像素操作 +slug: Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas +tags: + - Canvas +translation_of: Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Advanced_animations", "Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility")}}</div> + +<div class="summary"> +<p>到目前为止,我们尚未深入了解Canvas画布真实像素的原理,事实上,你可以直接通过ImageData对象操纵像素数据,直接读取或将数据数组写入该对象中。稍后我们也将深入了解如何控制图像使其平滑(反锯齿)以及如何从Canvas画布中保存图像。</p> +</div> + +<h2 id="ImageData_对象">ImageData 对象</h2> + +<p>{{domxref("ImageData")}}对象中存储着canvas对象真实的像素数据,它包含以下几个只读属性:</p> + +<p><strong><code>width</code></strong></p> + +<dl> + <dd>图片宽度,单位是像素</dd> + <dt><code>height</code></dt> + <dd>图片高度,单位是像素</dd> + <dt><code>data</code></dt> + <dd>{{jsxref("Uint8ClampedArray")}}类型的一维数组,包含着RGBA格式的整型数据,范围在0至255之间(包括255)。</dd> +</dl> + +<p>data属性返回一个 {{jsxref("Uint8ClampedArray")}},它可以被使用作为查看初始像素数据。每个像素用4个1bytes值(按照红,绿,蓝和透明值的顺序; 这就是"RGBA"格式) 来代表。每个颜色值部份用0至255来代表。每个部份被分配到一个在数组内连续的索引,左上角像素的红色部份在数组的索引0位置。像素从左到右被处理,然后往下,遍历整个数组。</p> + +<p>{{jsxref("Uint8ClampedArray")}} 包含高度 × 宽度 × 4 bytes数据,索引值从0到(<code style="font-style: normal;">高度</code>×<font face="Consolas, Monaco, Andale Mono, monospace">宽度</font>×4)-1</p> + +<p>例如,要读取图片中位于第50行,第200列的像素的蓝色部份,你会写以下代码:</p> + +<pre class="notranslate">blueComponent = imageData.data[((50 * (imageData.width * 4)) + (200 * 4)) + 2];</pre> + +<p>根据行、列读取某像素点的R/G/B/A值的公式:</p> + +<pre class="notranslate">imageData.data[((50 * (imageData.width * 4)) + (200 * 4)) + 0/1/2/3];</pre> + +<p>你可能用会使用<span style="font-family: consolas,monaco,andale mono,monospace;">Uint8ClampedArray.length属性来读取</span>像素数组的大小(以bytes为单位):</p> + +<pre class="brush: js notranslate">var numBytes = imageData.data.length; +</pre> + +<h2 id="创建一个ImageData对象">创建一个<code>ImageData对象</code></h2> + +<p>去创建一个新的,空白的ImageData<code>对象</code>,你应该会使用{{domxref("CanvasRenderingContext2D.createImageData", "createImageData()")}} 方法。有2个版本的<span style="font-family: consolas,monaco,andale mono,monospace;">createImageData()方法。</span></p> + +<pre class="brush: js notranslate">var myImageData = ctx.createImageData(width, height);</pre> + +<p>上面代码创建了一个新的具体特定尺寸的ImageData<code>对象</code>。所有像素被预设为透明黑。</p> + +<p>你也可以创建一个被anotherImageData<code>对象</code>指定的相同像素的ImageData<code>对象</code>。这个新的<code>对象</code>像素全部被预设为透明黑。这个并非复制了图片数据。</p> + +<pre class="brush: js notranslate">var myImageData = ctx.createImageData(anotherImageData);</pre> + +<h2 id="得到场景像素数据">得到场景像素数据</h2> + +<p>为了获得一个包含画布场景像素数据的ImageData对像,你可以用<span style="font-family: consolas,monaco,andale mono,monospace;">getImageData()方法:</span></p> + +<pre class="brush: js notranslate">var myImageData = ctx.getImageData(left, top, width, height);</pre> + +<p>这个方法会返回一个ImageData<code>对象</code>,它代表了画布区域的<code>对象</code>数据,此画布的四个角落分别表示为(left, top), (left + width, top), (left, top + height), 以及(left + width, top + height)四个点。这些坐标点被设定为画布坐标空间元素。</p> + +<div class="note"> +<p>注:任何在画布以外的元素都会被返回成一个透明黑的ImageData对像。</p> +</div> + +<p>这个方法也会在文章<a href="/en-US/docs/Web/API/Canvas_API/Manipulating_video_using_canvas">用画布操作视频</a>中展示。</p> + +<h3 id="A_color_picker" name="A_color_picker"><strong>颜色选择器</strong></h3> + +<p>在这个例子里面,我们会使用<code style="font-style: normal;">getImageData()去展示鼠标光标下的颜色。为此,我们要当前鼠标的位置,记为layerX和layerY,然后我们去查询getImageData()给我们提供的在那个位置的像数数组里面的像素数据。最后我们使用数组数据去设置背景颜色和<div>的文字去展示颜色值。</code></p> + +<div class="hidden"> +<pre class="brush: html; notranslate"><canvas id="canvas" width="300" height="227" style="float:left"></canvas> +<div id="color" style="width:200px;height:50px;float:left"></div> +</pre> + +<pre class="brush: js; notranslate">var img = new Image(); +img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg'; +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +img.onload = function() { + ctx.drawImage(img, 0, 0); + img.style.display = 'none'; +}; +var color = document.getElementById('color'); +function pick(event) { + var x = event.layerX; + var y = event.layerY; + var pixel = ctx.getImageData(x, y, 1, 1); + var data = pixel.data; + var rgba = 'rgba(' + data[0] + ',' + data[1] + + ',' + data[2] + ',' + (data[3] / 255) + ')'; + color.style.background = rgba; + color.textContent = rgba; +} +canvas.addEventListener('mousemove', pick); +</pre> +</div> + +<pre class="brush: js notranslate">var img = new Image(); +img.crossOrigin = 'anonymous'; +img.src = './assets/rhino.jpg'; +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); +img.onload = function() { + ctx.drawImage(img, 0, 0); + img.style.display = 'none'; +}; +var hoveredColor = document.getElementById('hovered-color'); +var selectedColor = document.getElementById('selected-color'); + + +function pick(event, destination) { + var x = event.layerX; + var y = event.layerY; + var pixel = ctx.getImageData(x, y, 1, 1); + var data = pixel.data; + + const rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`; + destination.style.background = rgba; + destination.textContent = rgba; + + return rgba; +} + +canvas.addEventListener('mousemove', function(event) { + pick(event, hoveredColor); +}); +canvas.addEventListener('click', function(event) { + pick(event, selectedColor); +});</pre> + +<p>{{EmbedGHLiveSample("dom-examples/canvas/pixel-manipulation/color-picker.html", '100%', 300)}}</p> + +<h2 id="在场景中写入像素数据">在场景中写入像素数据</h2> + +<p>你可以用putImageData()方法去对场景进行像素数据的写入。</p> + +<pre class="brush: js notranslate">ctx.putImageData(myImageData, dx, dy); +</pre> + +<p>dx和dy参数表示你希望在场景内左上角绘制的像素数据所得到的设备坐标。</p> + +<p>例如,为了在场景内左上角绘制myImageData代表的图片,你可以写如下的代码:</p> + +<pre class="brush: js notranslate">ctx.putImageData(myImageData, 0, 0); +</pre> + +<h3 id="sect1"></h3> + +<h3 id="Grayscaling_and_inverting_colors" name="Grayscaling_and_inverting_colors">图片灰度和反相颜色</h3> + +<p>在这个例子里,我们遍历所有像素以改变他们的数值。然后我们将被修改的像素数组通过putImageData()放回到画布中去。invert函数仅仅是去减掉颜色的最大色值255.grayscale函数仅仅是用红绿和蓝的平均值。你也可以用加权平均,例如<span style="font-family: consolas,monaco,andale mono,monospace;">x = 0.299r + 0.587g + 0.114b这个公式。更多资料请参考维基百科的</span><a href="http://en.wikipedia.org/wiki/Grayscale">Grayscale</a>。</p> + +<div class="hidden"> +<pre class="brush: html; notranslate"><canvas id="canvas" width="300" height="227"></canvas> +<div> + <input id="grayscalebtn" value="Grayscale" type="button"> + <input id="invertbtn" value="Invert" type="button"> +</div> +</pre> + +<pre class="brush: js notranslate">var img = new Image(); +img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg'; +img.onload = function() { + draw(this); +}; + +function draw(img) { + var canvas = document.getElementById('canvas'); + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + img.style.display = 'none'; + var imageData = ctx.getImageData(0,0,canvas.width, canvas.height); + var data = imageData.data; + + var invert = function() { + for (var i = 0; i < data.length; i += 4) { + data[i] = 255 - data[i]; // red + data[i + 1] = 255 - data[i + 1]; // green + data[i + 2] = 255 - data[i + 2]; // blue + } + ctx.putImageData(imageData, 0, 0); + }; + + var grayscale = function() { + for (var i = 0; i < data.length; i += 4) { + var avg = (data[i] + data[i +1] + data[i +2]) / 3; + data[i] = avg; // red + data[i + 1] = avg; // green + data[i + 2] = avg; // blue + } + ctx.putImageData(imageData, 0, 0); + }; + + var invertbtn = document.getElementById('invertbtn'); + invertbtn.addEventListener('click', invert); + var grayscalebtn = document.getElementById('grayscalebtn'); + grayscalebtn.addEventListener('click', grayscale); +} +</pre> +</div> + + + +<pre class="brush: js notranslate">var img = new Image(); +img.crossOrigin = 'anonymous'; +img.src = './assets/rhino.jpg'; + +var canvas = document.getElementById('canvas'); +var ctx = canvas.getContext('2d'); + +img.onload = function() { + ctx.drawImage(img, 0, 0); +}; + +var original = function() { + ctx.drawImage(img, 0, 0); +}; + +var invert = function() { + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + for (var i = 0; i < data.length; i += 4) { + data[i] = 255 - data[i]; // red + data[i + 1] = 255 - data[i + 1]; // green + data[i + 2] = 255 - data[i + 2]; // blue + } + ctx.putImageData(imageData, 0, 0); +}; + +var grayscale = function() { + ctx.drawImage(img, 0, 0); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + for (var i = 0; i < data.length; i += 4) { + var avg = (data[i] + data[i + 1] + data[i + 2]) / 3; + data[i] = avg; // red + data[i + 1] = avg; // green + data[i + 2] = avg; // blue + } + ctx.putImageData(imageData, 0, 0); +}; + +const inputs = document.querySelectorAll('[name=color]'); +for (const input of inputs) { + input.addEventListener("change", function(evt) { + switch (evt.target.value) { + case "inverted": + return invert(); + case "grayscale": + return grayscale(); + default: + return original(); + } + }); +}</pre> + +<p>{{EmbedGHLiveSample("dom-examples/canvas/pixel-manipulation/color-manipulation.html", '100%', 300)}}</p> + +<h2 id="缩放和反锯齿">缩放和反锯齿</h2> + +<p>在{{domxref("CanvasRenderingContext2D.drawImage", "drawImage()")}} 方法, 第二个画布和{{domxref("CanvasRenderingContext2D.imageSmoothingEnabled", "imageSmoothingEnabled")}} 属性的帮助下,我们可以放大显示我们的图片及看到详情内容。</p> + +<p>我们得到鼠标的位置并裁剪出距左和上5像素,距右和下5像素的图片。然后我们将这幅图复制到另一个画布然后将图片调整到我们想要的大小。在缩放画布里,我们将10×10像素的对原画布的裁剪调整为 200×200 。</p> + +<pre class="brush: js notranslate">zoomctx.drawImage(canvas, + Math.abs(x - 5), Math.abs(y - 5), + 10, 10, 0, 0, 200, 200);</pre> + +<p>因为反锯齿默认是启用的,我们可能想要关闭它以看到清楚的像素。你可以通过切换勾选框来看到<span style="font-family: consolas,monaco,andale mono,monospace;">imageSmoothingEnabled属性的效果(不同浏览器需要不同前缀)。</span></p> + +<h6 class="hidden" id="Zoom_example">Zoom example</h6> + +<pre class="brush: html; notranslate"><canvas id="canvas" width="300" height="227"></canvas> +<canvas id="zoom" width="300" height="227"></canvas> +<div> +<label for="smoothbtn"> + <input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn"> + Enable image smoothing +</label> +</div> +</pre> + +<pre class="brush: js notranslate">var img = new Image(); +img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg'; +img.onload = function() { + draw(this); +}; + +function draw(img) { + var canvas = document.getElementById('canvas'); + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + img.style.display = 'none'; + var zoomctx = document.getElementById('zoom').getContext('2d'); + + var smoothbtn = document.getElementById('smoothbtn'); + var toggleSmoothing = function(event) { + zoomctx.imageSmoothingEnabled = this.checked; + zoomctx.mozImageSmoothingEnabled = this.checked; + zoomctx.webkitImageSmoothingEnabled = this.checked; + zoomctx.msImageSmoothingEnabled = this.checked; + }; + smoothbtn.addEventListener('change', toggleSmoothing); + + var zoom = function(event) { + var x = event.layerX; + var y = event.layerY; + zoomctx.drawImage(canvas, + Math.abs(x - 5), + Math.abs(y - 5), + 10, 10, + 0, 0, + 200, 200); + }; + + canvas.addEventListener('mousemove', zoom); +}</pre> + +<p>{{ EmbedLiveSample('Zoom_example', 620, 490) }}</p> + +<h2 id="保存图片">保存图片</h2> + +<p>{{domxref("HTMLCanvasElement")}} 提供一个<span style="font-family: consolas,monaco,andale mono,monospace;">toDataURL()方法,此方法在保存图片的时候非常有用。它返回一个包含被类型参数规定的图像表现格式的<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs">数据链接</a>。返回的图片分辨率是96dpi。</span></p> + +<dl> + <dt>{{domxref("HTMLCanvasElement.toDataURL", "canvas.toDataURL('image/png')")}}</dt> + <dd>默认设定。创建一个PNG图片。</dd> + <dd>Default setting. Creates a PNG image.</dd> + <dt>{{domxref("HTMLCanvasElement.toDataURL", "canvas.toDataURL('image/jpeg', quality)")}}</dt> + <dd>创建一个JPG图片。你可以有选择地提供从0到1的品质量,1表示最好品质,0基本不被辨析但有比较小的文件大小。</dd> +</dl> + +<p>当你从画布中生成了一个数据链接,例如,你可以将它用于任何{{HTMLElement("image")}}元素,或者将它放在一个有download属性的超链接里用于保存到本地。</p> + +<p>你也可以从画布中创建一个{{domxref("Blob")}}对像。</p> + +<dl> + <dt>{{domxref("HTMLCanvasElement.toBlob", "canvas.toBlob(callback, type, encoderOptions)")}}</dt> + <dd>这个创建了一个在画布中的代表图片的<span style="font-family: consolas,monaco,andale mono,monospace;">Blob对像。</span></dd> +</dl> + +<h2 id="参见">参见</h2> + +<ul> + <li>{{domxref("ImageData")}}</li> + <li><a href="/en-US/docs/Web/API/Canvas_API/Manipulating_video_using_canvas">Manipulating video using canvas</a></li> + <li><a href="https://codepo8.github.io/canvas-images-and-pixels/">Canvas, images and pixels – by Christian Heilmann</a></li> +</ul> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Advanced_animations", "Web/API/Canvas_API/Tutorial/Hit_regions_and_accessibility")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html b/files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html new file mode 100644 index 0000000000..d8e65e4dc4 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/transformations/index.html @@ -0,0 +1,278 @@ +--- +title: 变形 Transformations +slug: Web/API/Canvas_API/Tutorial/Transformations +tags: + - Canvas + - HTML + - HTML5 + - Web + - 图形 + - 教程 +translation_of: Web/API/Canvas_API/Tutorial/Transformations +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}</div> + +<p class="summary">在本教程前面的部分中,我们已经了解了Canvas网格和坐标空间。到目前为止,我们只是根据我们的需要使用默认的网格,改变整个画布的大小。变形是一种更强大的方法,可以将原点移动到另一点、对网格进行旋转和缩放。</p> + +<h2 id="Saving_and_restoring_state" name="Saving_and_restoring_state">状态的保存和恢复 Saving and restoring state</h2> + +<p>在了解变形之前,我先介绍两个在你开始绘制复杂图形时必不可少的方法。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.save", "save()")}}</dt> + <dd>保存画布(canvas)的所有状态</dd> + <dt>{{domxref("CanvasRenderingContext2D.restore", "restore()")}}</dt> + <dd>save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。</dd> +</dl> + +<p><span style="line-height: 1.5;">Canvas状态存储在栈中,每当<code>save()</code>方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:</span></p> + +<ul> + <li>当前应用的变形(即移动,旋转和缩放,见下)</li> + <li>以及下面这些属性:{{domxref("CanvasRenderingContext2D.strokeStyle", "strokeStyle")}}, {{domxref("CanvasRenderingContext2D.fillStyle", "fillStyle")}}, {{domxref("CanvasRenderingContext2D.globalAlpha", "globalAlpha")}}, {{domxref("CanvasRenderingContext2D.lineWidth", "lineWidth")}}, {{domxref("CanvasRenderingContext2D.lineCap", "lineCap")}}, {{domxref("CanvasRenderingContext2D.lineJoin", "lineJoin")}}, {{domxref("CanvasRenderingContext2D.miterLimit", "miterLimit")}}, {{domxref("CanvasRenderingContext2D.lineDashOffset", "lineDashOffset")}}, {{domxref("CanvasRenderingContext2D.shadowOffsetX", "shadowOffsetX")}}, {{domxref("CanvasRenderingContext2D.shadowOffsetY", "shadowOffsetY")}}, {{domxref("CanvasRenderingContext2D.shadowBlur", "shadowBlur")}}, {{domxref("CanvasRenderingContext2D.shadowColor", "shadowColor")}}, {{domxref("CanvasRenderingContext2D.globalCompositeOperation", "globalCompositeOperation")}}, {{domxref("CanvasRenderingContext2D.font", "font")}}, {{domxref("CanvasRenderingContext2D.textAlign", "textAlign")}}, {{domxref("CanvasRenderingContext2D.textBaseline", "textBaseline")}}, {{domxref("CanvasRenderingContext2D.direction", "direction")}}, {{domxref("CanvasRenderingContext2D.imageSmoothingEnabled", "imageSmoothingEnabled")}}</li> + <li>当前的<a href="/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing#Clipping_paths">裁切路径(clipping path)</a>,会在下一节介绍</li> +</ul> + +<p>你可以调用任意多次 <code>save</code>方法。每一次调用 <code>restore</code> 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。</p> + +<h3 id="A_save_and_restore_canvas_state_example" name="A_save_and_restore_canvas_state_example"><code>save</code> 和 <code>restore</code> 的应用例子</h3> + +<p><img alt="" class="internal" src="/@api/deki/files/104/=Canvas_savestate.png" style="float: right;">我们尝试用这个连续矩形的例子来描述 canvas 的状态栈是如何工作的。</p> + +<p>第一步是用默认设置画一个大四方形,然后保存一下状态。改变填充颜色画第二个小一点的蓝色四方形,然后再保存一下状态。再次改变填充颜色绘制更小一点的半透明的白色四方形。</p> + +<p>到目前为止所做的动作和前面章节的都很类似。不过一旦我们调用 <code>restore</code>,状态栈中最后的状态会弹出,并恢复所有设置。如果不是之前用 <code>save </code>保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。</p> + +<p>当第二次调用 <code>restore</code> 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。</p> + +<p>{{EmbedLiveSample("A_save_and_restore_canvas_state_example", "180", "180", "https://mdn.mozillademos.org/files/249/Canvas_savestate.png")}}</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + ctx.fillRect(0,0,150,150); // 使用默认设置绘制一个矩形 + ctx.save(); // 保存默认状态 + + ctx.fillStyle = '#09F' // 在原有配置基础上对颜色做改变 + ctx.fillRect(15,15,120,120); // 使用新的设置绘制一个矩形 + + ctx.save(); // 保存当前状态 + ctx.fillStyle = '#FFF' // 再次改变颜色配置 + ctx.globalAlpha = 0.5; + ctx.fillRect(30,30,90,90); // 使用新的配置绘制一个矩形 + + ctx.restore(); // 重新加载之前的颜色状态 + ctx.fillRect(45,45,60,60); // 使用上一次的配置绘制一个矩形 + + ctx.restore(); // 加载默认颜色配置 + ctx.fillRect(60,60,30,30); // 使用加载的配置绘制一个矩形 +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<h2 id="Translating" name="Translating">移动 Translating</h2> + +<p><img alt="" class="internal" src="/@api/deki/files/85/=Canvas_grid_translate.png" style="float: right;"></p> + +<p>我们先介绍 <code>translate </code>方法,它用来移动 canvas 和它的原点到一个不同的位置。</p> + +<dl> + <dt><code>translate(x, y)</code></dt> + <dd><code>translate </code>方法接受两个参数。<var>x </var>是左右偏移量,<var>y</var> 是上下偏移量,如右图所示。</dd> +</dl> + +<p>在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。又,如果你是在一个循环中做位移但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。</p> + +<h3 id="A_translate_example" name="A_translate_example"><code>translate</code> 的例子</h3> + +<p>这个例子显示了一些移动 canvas 原点的好处。如果不使用 <code>translate </code>方法,那么所有矩形都将被绘制在相同的位置(0,0)。<code>translate </code>方法同时让我们可以任意放置这些图案,而不需要在 <code>fillRect()</code> 方法中手工调整坐标值,既好理解也方便使用。</p> + +<p>我在 <code>draw </code>方法中调用 <code>fillRect()</code> 方法 9 次,用了 2 层循环。每一次循环,先移动 canvas ,画螺旋图案,然后恢复到原始状态。</p> + +<pre class="brush: js; highlight:[7] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + ctx.save(); + ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)'; + ctx.translate(10 + j * 50, 10 + i * 50); + ctx.fillRect(0, 0, 25, 25); + ctx.restore(); + } + } +} +</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_translate_example", "160", "160", "https://mdn.mozillademos.org/files/9857/translate.png")}}</p> + +<h2 id="Rotating" name="Rotating">旋转 Rotating</h2> + +<p><img alt="" class="internal" src="/@api/deki/files/84/=Canvas_grid_rotate.png" style="float: right;"></p> + +<p>第二个介绍 <code>rotate </code>方法,它用于以原点为中心旋转 canvas。</p> + +<dl> + <dt><code>rotate(angle)</code></dt> + <dd>这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。</dd> +</dl> + +<p>旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 <code>translate </code>方法。</p> + +<h3 id="A_rotate_example" name="A_rotate_example"><code>rotate</code> 的例子</h3> + +<p><img alt="" class="internal" src="/@api/deki/files/103/=Canvas_rotate.png" style="float: right;"></p> + +<p>在这个例子里,见右图,我用 <code>rotate </code>方法来画圆并构成圆形图案。当然你也可以分别计算出 <var>x</var> 和 <var>y</var> 坐标(<code>x = r*Math.cos(a); y = r*Math.sin(a)</code>)。这里无论用什么方法都无所谓的,因为我们画的是圆。计算坐标的结果只是旋转圆心位置,而不是圆本身。即使用 <code>rotate </code>旋转两者,那些圆看上去还是一样的,不管它们绕中心旋转有多远。</p> + +<p>这里我们又用到了两层循环。第一层循环决定环的数量,第二层循环决定每环有多少个点。每环开始之前,我都保存一下 canvas 的状态,这样恢复起来方便。每次画圆点,我都以一定夹角来旋转 canvas,而这个夹角则是由环上的圆点数目的决定的。最里层的环有 6 个圆点,这样,每次旋转的夹角就是 360/6 = 60 度。往外每一环的圆点数目是里面一环的 2 倍,那么每次旋转的夹角随之减半。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="300" height="200"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_rotate_example", "310", "210", "https://mdn.mozillademos.org/files/9859/rotate.png")}}</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + ctx.translate(75,75); + + for (var i=1;i<6;i++){ // Loop through rings (from inside to out) + ctx.save(); + ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)'; + + for (var j=0;j<i*6;j++){ // draw individual dots + ctx.rotate(Math.PI*2/(i*6)); + ctx.beginPath(); + ctx.arc(0,i*12.5,5,0,Math.PI*2,true); + ctx.fill(); + } + + ctx.restore(); + } +} +</pre> + +<h2 id="Scaling" name="Scaling">缩放 Scaling</h2> + +<p>接着是缩放。我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。</p> + +<dl> + <dt><code><strong>scale(x, y)</strong></code></dt> + <dd><code>scale </code> 方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会缩小图形, 如果比1大会放大图形。默认值为1, 为实际大小。</dd> +</dl> + +<p>画布初始情况下, 是以左上角坐标为原点的第一象限。如果参数为负实数, 相当于以x 或 y轴作为对称轴镜像反转(例如, 使用<code>translate(0,canvas.height); scale(1,-1);</code> 以y轴作为对称轴镜像反转, 就可得到著名的笛卡尔坐标系,左下角为原点)。</p> + +<p>默认情况下,canvas 的 1 个单位为 1 个像素。举例说,如果我们设置缩放因子是 0.5,1 个单位就变成对应 0.5 个像素,这样绘制出来的形状就会是原先的一半。同理,设置为 2.0 时,1 个单位就对应变成了 2 像素,绘制的结果就是图形放大了 2 倍。</p> + +<h3 id="A_scale_example" name="A_scale_example"><code>scale</code> 的例子</h3> + +<p>这最后的例子里,我们用不同的缩放方式来画两个图形。</p> + +<pre class="brush: js notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + // draw a simple rectangle, but scale it. + ctx.save(); + ctx.scale(10, 3); + ctx.fillRect(1, 10, 10, 10); + ctx.restore(); + + // mirror horizontally + ctx.scale(-1, 1); + ctx.font = '48px serif'; + ctx.fillText('MDN', -135, 120); +}</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="150" height="150"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("A_scale_example", "160", "160", "https://mdn.mozillademos.org/files/9861/scale.png")}}</p> + +<h2 id="Transforms" name="Transforms">变形 Transforms</h2> + +<p>最后一个方法允许对变形矩阵直接修改。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.transform", "transform(a, b, c, d, e, f)")}}</dt> + <dd>这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵,如下面的矩阵所示:<math><semantics><mrow><mo>[</mo><mtable columnalign="center center center" rowspacing="0.5ex"><mtr><mtd><mi>a</mi></mtd><mtd><mi>c</mi></mtd><mtd><mi>e</mi></mtd></mtr><mtr><mtd><mi>b</mi></mtd><mtd><mi>d</mi></mtd><mtd><mi>f</mi></mtd></mtr><mtr><mtd><mn>0</mn></mtd><mtd><mn>0</mn></mtd><mtd><mn>1</mn></mtd></mtr></mtable><mo>]</mo></mrow><annotation encoding="TeX">\left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]</annotation></semantics></math></dd> + <dd>如果任意一个参数是<code><a href="https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity" title="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity">Infinity</a></code>,变形矩阵也必须被标记为无限大,否则会抛出异常。</dd> +</dl> + +<p>这个函数的参数各自代表如下:</p> + +<dl> + <dt><code>a (m11)</code></dt> + <dd>水平方向的缩放</dd> + <dt><code>b(m12)</code></dt> + <dd>竖直方向的倾斜偏移</dd> + <dt><code>c(m21)</code></dt> + <dd>水平方向的倾斜偏移</dd> + <dt><code>d(m22)</code></dt> + <dd>竖直方向的缩放</dd> + <dt><code>e(dx)</code></dt> + <dd>水平方向的移动</dd> + <dt><code>f(dy)</code></dt> + <dd>竖直方向的移动</dd> +</dl> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.setTransform", "setTransform(a, b, c, d, e, f)")}}</dt> + <dd>这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 <code>transform </code>方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。</dd> +</dl> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.resetTransform", "resetTransform()")}}</dt> + <dd>重置当前变形为单位矩阵,它和调用以下语句是一样的:<code>ctx.setTransform(1, 0, 0, 1, 0, 0);</code></dd> +</dl> + +<h3 id="transform_setTransform_的例子"><code>transform</code> / <code>setTransform</code> 的例子</h3> + +<dl> +</dl> + +<pre class="brush: js; highlight:[12,15] notranslate">function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + + var sin = Math.sin(Math.PI/6); + var cos = Math.cos(Math.PI/6); + ctx.translate(100, 100); + var c = 0; + for (var i=0; i <= 12; i++) { + c = Math.floor(255 / 12 * i); + ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")"; + ctx.fillRect(0, 0, 100, 10); + ctx.transform(cos, sin, -sin, cos, 0, 0); + } + + ctx.setTransform(-1, 0, 0, 1, 100, 100); + ctx.fillStyle = "rgba(255, 128, 255, 0.5)"; + ctx.fillRect(0, 50, 100, 100); +}</pre> + +<div class="hidden"> +<pre class="brush: html notranslate"><canvas id="canvas" width="200" height="250"></canvas></pre> + +<pre class="brush: js notranslate">draw();</pre> +</div> + +<p>{{EmbedLiveSample("transform_setTransform_的例子", "230", "280", "https://mdn.mozillademos.org/files/255/Canvas_transform.png")}}</p> + +<p><br> + {{PreviousNext("Web/API/Canvas_API/Tutorial/Using_images", "Web/API/Canvas_API/Tutorial/Compositing")}}</p> diff --git a/files/zh-cn/web/api/canvas_api/tutorial/using_images/index.html b/files/zh-cn/web/api/canvas_api/tutorial/using_images/index.html new file mode 100644 index 0000000000..2d484b5d15 --- /dev/null +++ b/files/zh-cn/web/api/canvas_api/tutorial/using_images/index.html @@ -0,0 +1,328 @@ +--- +title: 使用图像 Using images +slug: Web/API/Canvas_API/Tutorial/Using_images +tags: + - Canvas +translation_of: Web/API/Canvas_API/Tutorial/Using_images +--- +<div>{{CanvasSidebar}} {{PreviousNext("Web/API/Canvas_API/Tutorial/Drawing_text", "Web/API/Canvas_API/Tutorial/Transformations")}}</div> + +<p>canvas更有意思的一项特性就是图像操作能力。可以用于动态的图像合成或者作为图形的背景,以及游戏界面(Sprites)等等。浏览器支持的任意格式的外部图片都可以使用,比如PNG、GIF或者JPEG。 你甚至可以将同一个页面中其他canvas元素生成的图片作为图片源。</p> + +<p><span style="line-height: 1.5;">引入图像到canvas里需要以下两步基本操作:</span></p> + +<ol> + <li>获得一个指向{{domxref("HTMLImageElement")}}的对象或者另一个canvas元素的引用作为源,也可以通过提供一个URL的方式来使用图片(参见<a href="http://www.html5canvastutorials.com/tutorials/html5-canvas-images/">例子</a>)</li> + <li>使用<code>drawImage()</code>函数将图片绘制到画布上</li> +</ol> + +<p>我们来看看具体是怎么做的。</p> + +<h2 id="获得需要绘制的图片">获得需要绘制的图片</h2> + +<p>canvas的API可以使用下面这些类型中的一种作为图片的源:</p> + +<dl> + <dt><strong style="font-weight: bold;">{{domxref("HTMLImageElement")}}</strong></dt> + <dd>这些图片是由<code>Image()函数构造出来的,或者任何的{{HTMLElement("img")}}元素</code></dd> + <dt><strong style="font-weight: bold; line-height: 1.5;">{{domxref("HTMLVideoElement")}}</strong></dt> + <dd>用一个HTML的 <span style="line-height: 1.5;">{{HTMLElement("video")}}元素作为你的图片源,可以从视频中抓取当前帧作为一个图像</span></dd> + <dt><strong style="font-weight: bold;">{{domxref("HTMLCanvasElement")}}</strong></dt> + <dd>可以使用另一个 {{HTMLElement("canvas")}} 元素作为你的图片源。</dd> + <dt><strong style="font-weight: bold;">{{domxref("ImageBitmap")}}</strong></dt> + <dd>这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其它几种源中生成。</dd> +</dl> + +<p>这些源统一由 {{domxref("CanvasImageSource")}}类型来引用。</p> + +<p>有几种方式可以获取到我们需要在canvas上使用的图片。</p> + +<h3 id="Using_images_which_are_on_the_same_page" name="Using_images_which_are_on_the_same_page">使用相同页面内的图片</h3> + +<p>我们可以通过下列方法的一种来获得与canvas相同页面内的图片的引用:</p> + +<ul> + <li>{{domxref("document.images")}}集合</li> + <li> {{domxref("document.getElementsByTagName()")}}方法</li> + <li>如果你知道你想使用的指定图片的ID,你可以用{{domxref("document.getElementById()")}}获得这个图片</li> +</ul> + +<h3 id="Using_other_canvas_elements" name="Using_other_canvas_elements">使用其它域名下的图片</h3> + +<p>在 {{domxref("HTMLImageElement")}}上使用<a href="https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes">crossOrigin</a>属性,你可以请求加载其它域名上的图片。如果图片的服务器允许跨域访问这个图片,那么你可以使用这个图片而不污染canvas,否则,使用这个图片将会<a href="https://developer.mozilla.org/zh-CN/docs/CORS_Enabled_Image#.E4.BB.80.E4.B9.88.E6.98.AF.22.E8.A2.AB.E6.B1.A1.E6.9F.93.22.E7.9A.84canvas">污染canvas</a>。</p> + +<h3 id="Using_other_canvas_elements" name="Using_other_canvas_elements">使用其它 canvas 元素</h3> + +<p><span style="line-height: 1.5;">和引用页面内的图片类似地,用 </span><code style="font-style: normal; line-height: 1.5;"><a href="/en/DOM/document.getElementsByTagName" title="en/DOM/document.getElementsByTagName">document.getElementsByTagName</a> </code><span style="line-height: 1.5;">或 </span><code style="font-style: normal; line-height: 1.5;"><a href="/en/DOM/document.getElementById" title="en/DOM/document.getElementById">document.getElementById</a> </code><span style="line-height: 1.5;">方法来获取其它 canvas 元素。但你引入的应该是已经准备好的 canvas。</span></p> + +<p><span style="line-height: 1.5;">一个常用的应用就是将第二个canvas作为另一个大的 canvas 的缩略图。</span></p> + +<h3 id="Creating_an_image_from_scratch" name="Creating_an_image_from_scratch">由零开始创建图像</h3> + +<p>或者我们可以用脚本创建一个新的 {{domxref("HTMLImageElement")}} 对象。要实现这个方法,我们可以使用很方便的<code>Image()构造函数。</code></p> + +<pre class="brush: js notranslate">var img = new Image(); // 创建一个<img>元素 +img.src = 'myImage.png'; // 设置图片源地址 +</pre> + +<p>当脚本执行后,图片开始装载。</p> + +<p>若调用 <code>drawImage</code> 时,图片没装载完,那什么都不会发生(在一些旧的浏览器中可能会抛出异常)。因此你应该用load事件来保证不会在加载完毕之前使用这个图片:</p> + +<pre class="brush: js notranslate">var img = new Image(); // 创建img元素 +img.onload = function(){ + // 执行drawImage语句 +} +img.src = 'myImage.png'; // 设置图片源地址 +</pre> + +<p><span style="line-height: 1.5;">如果你只用到一张图片的话,这已经够了。但一旦需要不止一张图片,那就需要更加复杂的处理方法,但图片预加载策略超出本教程的范围</span><span style="line-height: 1.5;">。</span></p> + +<h3 id="Embedding_an_image_via_data_url" name="Embedding_an_image_via_data:_url">通过 data: url 方式嵌入图像</h3> + +<p>我们还可以通过 <a class="external" href="http://en.wikipedia.org/wiki/Data:_URL">data:url</a> 方式来引用图像。Data urls 允许用一串 Base64 编码的字符串的方式来定义一个图片。</p> + +<pre class="brush: js notranslate">img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw=='; +</pre> + +<p>其优点就是图片内容即时可用,无须再到服务器兜一圈。(还有一个优点是,可以将 CSS,JavaScript,HTML 和 图片全部封装在一起,迁移起来十分方便。)缺点就是图像没法缓存,图片大的话内嵌的 url 数据会相当的长:</p> + +<h3 id="drawImage" name="drawImage">使用视频帧</h3> + +<p>你还可以使用{{HTMLElement("video")}} 中的视频帧(即便视频是不可见的)。例如,如果你有一个ID为“myvideo”的{{HTMLElement("video")}} 元素,你可以这样做:</p> + +<pre class="brush: js language-js notranslate" style="padding: 1em 0px 1em 30px; border-left-width: 6px; border-left-style: solid; font-family: Consolas, Monaco, 'Andale Mono', monospace; font-size: 14px; direction: ltr; white-space: normal; text-shadow: none; background-color: rgba(234, 239, 242, 0.247059);"><code class="language-js" style="font-family: Consolas, Monaco, 'Andale Mono', monospace; direction: ltr; white-space: pre; color: inherit; text-shadow: none;"><span class="keyword token" style="color: #0077aa;">function</span> <span class="function token">getMyVideo<span class="punctuation token" style="color: #999999;">(</span></span><span class="punctuation token" style="color: #999999;">)</span> <span class="punctuation token" style="color: #999999;">{</span> + <span class="keyword token" style="color: #0077aa;">var</span> canvas <span class="operator token" style="background: rgba(255, 255, 255, 0.498039); color: #a67f59;">=</span> document<span class="punctuation token" style="color: #999999;">.</span><span class="function token">getElementById<span class="punctuation token" style="color: #999999;">(</span></span><span class="string token" style="color: #669900;">'canvas'</span><span class="punctuation token" style="color: #999999;">)</span><span class="punctuation token" style="color: #999999;">;</span> + <span class="keyword token" style="color: #0077aa;">if</span> <span class="punctuation token" style="color: #999999;">(</span>canvas<span class="punctuation token" style="color: #999999;">.</span>getContext<span class="punctuation token" style="color: #999999;">)</span> <span class="punctuation token" style="color: #999999;">{</span> + <span class="keyword token" style="color: #0077aa;">var</span> ctx <span class="operator token" style="background: rgba(255, 255, 255, 0.498039); color: #a67f59;">=</span> canvas<span class="punctuation token" style="color: #999999;">.</span><span class="function token">getContext<span class="punctuation token" style="color: #999999;">(</span></span><span class="string token" style="color: #669900;">'2d'</span><span class="punctuation token" style="color: #999999;">)</span><span class="punctuation token" style="color: #999999;">;</span> + + <span class="keyword token" style="color: #0077aa;">return</span> document<span class="punctuation token" style="color: #999999;">.</span><span class="function token">getElementById<span class="punctuation token" style="color: #999999;">(</span></span><span class="string token" style="color: #669900;">'myvideo'</span><span class="punctuation token" style="color: #999999;">)</span><span class="punctuation token" style="color: #999999;">;</span> + <span class="punctuation token" style="color: #999999;">}</span> +<span class="punctuation token" style="color: #999999;">}</span></code></pre> + +<p>它将为这个视频返回{{domxref("HTMLVideoElement")}}对象,正如我们前面提到的,它可以作为我们的Canvas图片源。</p> + +<h2 id="drawImage" name="drawImage">绘制图片</h2> + +<p><span style="line-height: 1.5;">一旦获得了源图对象,我们就可以使用 </span><code style="font-style: normal; line-height: 1.5;">drawImage</code><span style="line-height: 1.5;"> 方法将它渲染到 canvas 里。</span><code style="font-style: normal; line-height: 1.5;">drawImage</code><span style="line-height: 1.5;"> 方法有三种形态,下面是最基础的一种。</span></p> + +<dl> + <dt><strong><code>drawImage(image, x, y)</code></strong></dt> + <dd><span style="line-height: 1.5;">其中 </span><code style="font-style: normal; line-height: 1.5;">image</code><span style="line-height: 1.5;"> 是 image 或者 canvas 对象,</span><code style="font-style: normal; line-height: 1.5;">x</code><span style="line-height: 1.5;"> 和 </span><code style="font-style: normal; line-height: 1.5;">y 是其在目标 canvas 里的起始坐标。</code></dd> +</dl> + +<div class="note"> +<p>SVG图像必须在 <svg> 根指定元素的宽度和高度。</p> +</div> + +<h3 id="Example_A_simple_line_graph" name="Example_A_simple_line_graph">例子:一个简单的线图</h3> + +<p><img alt="" class="internal" src="/@api/deki/files/58/=Canvas_backdrop.png" style="float: right;"></p> + +<p>下面一个例子我用一个外部图像作为一线性图的背景。用背景图我们就不需要绘制复杂的背景,省下不少代码。这里只用到一个 image 对象,于是就在它的 <code>onload</code> 事件响应函数中触发绘制动作。<code>drawImage</code> 方法将背景图放置在 canvas 的左上角 (0,0) 处。</p> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="180" height="150"></canvas> + </body> +</html></pre> +</div> + +<pre class="brush: js notranslate"> function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + var img = new Image(); + img.onload = function(){ + ctx.drawImage(img,0,0); + ctx.beginPath(); + ctx.moveTo(30,96); + ctx.lineTo(70,66); + ctx.lineTo(103,76); + ctx.lineTo(170,15); + ctx.stroke(); + } + img.src = 'https://mdn.mozillademos.org/files/5395/backdrop.png'; + } +</pre> + +<p>结果看起来是这样的:</p> + +<p>{{EmbedLiveSample("Example_A_simple_line_graph", 220, 160, "https://mdn.mozillademos.org/files/206/Canvas_backdrop.png")}}</p> + +<h2 id="Scaling" name="Scaling">缩放 Scaling</h2> + +<p><code>drawImage</code> 方法的又一变种是增加了两个用于控制图像在 canvas 中缩放的参数。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.drawImage", "drawImage(image, x, y, width, height)")}}</dt> + <dd>这个方法多了2个参数:<code>width</code> 和 <code>height,</code>这两个参数用来控制 当向canvas画入时应该缩放的大小</dd> +</dl> + +<h3 id="Example_Tiling_an_image" name="Example_Tiling_an_image"><font face="Consolas, Liberation Mono, Courier, monospace">例子:平铺图像</font></h3> + +<p><img alt="" class="internal" src="/@api/deki/files/106/=Canvas_scale_image.png" style="float: right;"></p> + +<p>在这个例子里,我会用一张图片像背景一样在 canvas 中以重复平铺开来。实现起来也很简单,只需要循环铺开经过缩放的图片即可。见下面的代码,第一层 <code>for</code> 循环是做行重复,第二层是做列重复的。图像大小被缩放至原来的三分之一,50x38 px。这种方法可以用来很好的达到背景图案的效果,在下面的教程中会看到。</p> + +<div class="note"> +<p>注意:图像可能会因为大幅度的缩放而变得起杂点或者模糊。如果您的图像里面有文字,那么最好还是不要进行缩放,因为那样处理之后很可能图像里的文字就会变得无法辨认了。</p> +</div> + +<div class="hidden"> +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + </body> +</html></pre> +</div> + +<pre class="brush: js notranslate"><code>function draw() { + var ctx = document.getElementById('canvas').getContext('2d'); + var img = new Image(); + img.onload = function(){ + for (var i=0;i<4;i++){ + for (var j=0;j<3;j++){ + ctx.drawImage(img,j*50,i*38,50,38); + } + } + }; + img.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg'; +}</code></pre> + +<p>结果看起来像这样:</p> + +<p>{{EmbedLiveSample("Example_Tiling_an_image", 160, 160, "https://mdn.mozillademos.org/files/251/Canvas_scale_image.png")}}</p> + +<h2 id="Slicing" name="Slicing">切片 Slicing</h2> + +<p><code>drawImage</code> 方法的第三个也是最后一个变种有8个新参数,用于控制做切片显示的。</p> + +<dl> + <dt>{{domxref("CanvasRenderingContext2D.drawImage", "drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)")}}</dt> + <dd>第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。其它8个参数最好是参照右边的图解,前4个是定义图像源的切片位置和大小,后4个则是定义切片的目标显示位置和大小。</dd> +</dl> + +<p><img alt="" class="internal" src="/@api/deki/files/79/=Canvas_drawimage.jpg" style="float: right;"></p> + +<p>切片是个做图像合成的强大工具。假设有一张包含了所有元素的图像,那么你可以用这个方法来合成一个完整图像。例如,你想画一张图表,而手上有一个包含所有必需的文字的 PNG 文件,那么你可以很轻易的根据实际数据的需要来改变最终显示的图表。这方法的另一个好处就是你不需要单独装载每一个图像。</p> + +<h3 id="Example_Framing_an_image" name="Example_Framing_an_image"><font face="Consolas, Liberation Mono, Courier, monospace">例子:相框</font></h3> + +<p><img alt="" class="internal" src="/@api/deki/files/80/=Canvas_drawimage2.jpg" style="float: right;"></p> + +<p>在这个例子里面我用到上面已经用过的犀牛图像,不过这次我要给犀牛头做个切片特写,然后合成到一个相框里面去。相框带有阴影效果,是一个以 24-bit PNG 格式保存的图像。因为 24-bit PNG 图像带有一个完整的 8-bit alpha 通道,与 GIF 和 8-bit PNG 不同,我可以将它放成背景而不必担心底色的问题。</p> + +<p>我用一个与上面用到的不同的方法来装载图像,直接将图像插入到 HTML 里面,然后通过 CSS 隐藏(<code>display:none</code>)它。两个图像我都赋了 <code>id</code> ,方便后面使用。看下面的脚本,相当简单,首先对犀牛头做好切片(第一次<code>drawImage</code>)放在 canvas 上,然后再上面套个相框(第二次<code>drawImage</code>)。</p> + +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <canvas id="canvas" width="150" height="150"></canvas> + <div style="display:none;"> + <img id="source" src="https://mdn.mozillademos.org/files/5397/rhino.jpg" width="300" height="227"> + <img id="frame" src="https://mdn.mozillademos.org/files/242/Canvas_picture_frame.png" width="132" height="150"> + </div> + </body> +</html></pre> + +<pre class="brush: js notranslate">function draw() { + var canvas = document.getElementById('canvas'); + var ctx = canvas.getContext('2d'); + + // Draw slice + ctx.drawImage(document.getElementById('source'), + 33,71,104,124,21,20,87,104); + + // Draw frame + ctx.drawImage(document.getElementById('frame'),0,0); +} +</pre> + +<p>{{EmbedLiveSample("Example_Framing_an_image", 160, 160, "https://mdn.mozillademos.org/files/226/Canvas_drawimage2.jpg")}}</p> + +<h2 id="Art_gallery_example" name="Art_gallery_example">示例:画廊 Art gallery example</h2> + +<p><img alt="" class="internal" src="/@api/deki/files/57/=Canvas_art_gallery.jpg" style="float: right;"></p> + +<p>我这一章最后的示例是弄一个小画廊。画廊由挂着几张画作的格子组成。当页面装载好之后,为每张画创建一个 canvas 元素并用加上画框然后插入到画廊中去。</p> + +<p>在我这个例子里面,所有“画”都是固定宽高的,画框也是。你可以做些改进,通过脚本用画的宽高来准确控制围绕它的画框的大小。</p> + +<p>下面的代码应该是蛮简单易懂的了。就是遍历图像对象数组,依次创建新的 canvas 元素并添加进去。可能唯一需要注意的,对于那些并不熟悉 DOM 的朋友来说,是 <code><a href="/En/DOM/Node.insertBefore" title="en/DOM/element.insertBefore">insertBefore</a></code> 方法的用法。<code>insertBefore</code> 是父节点(单元格)的方法,用于将新节点(canvas 元素)插入到我们想要插入的节点之前。</p> + +<pre class="brush: html notranslate"><html> + <body onload="draw();"> + <table> + <tr> + <td><img src="https://mdn.mozillademos.org/files/5399/gallery_1.jpg"></td> + <td><img src="https://mdn.mozillademos.org/files/5401/gallery_2.jpg"></td> + <td><img src="https://mdn.mozillademos.org/files/5403/gallery_3.jpg"></td> + <td><img src="https://mdn.mozillademos.org/files/5405/gallery_4.jpg"></td> + </tr> + <tr> + <td><img src="https://mdn.mozillademos.org/files/5407/gallery_5.jpg"></td> + <td><img src="https://mdn.mozillademos.org/files/5409/gallery_6.jpg"></td> + <td><img src="https://mdn.mozillademos.org/files/5411/gallery_7.jpg"></td> + <td><img src="https://mdn.mozillademos.org/files/5413/gallery_8.jpg"></td> + </tr> + </table> + <img id="frame" src="https://mdn.mozillademos.org/files/242/Canvas_picture_frame.png" width="132" height="150"> + </body> +</html></pre> + +<pre class="brush: css notranslate">body { + background: 0 -100px repeat-x url(https://mdn.mozillademos.org/files/5415/bg_gallery.png) #4F191A; + margin: 10px; +} + +img { + display: none; +} + +table { + margin: 0 auto; +} + +td { + padding: 15px; +}</pre> + +<pre class="brush: js notranslate">function draw() { + + // Loop through all images + for (i=0;i<document.images.length;i++){ + + // Don't add a canvas for the frame image + if (document.images[i].getAttribute('id')!='frame'){ + + // Create canvas element + canvas = document.createElement('CANVAS'); + canvas.setAttribute('width',132); + canvas.setAttribute('height',150); + + // Insert before the image + document.images[i].parentNode.insertBefore(canvas,document.images[i]); + + ctx = canvas.getContext('2d'); + + // Draw image to canvas + ctx.drawImage(document.images[i],15,20); + + // Add frame + ctx.drawImage(document.getElementById('frame'),0,0); + } + } +} +</pre> + +<p>{{EmbedLiveSample("Art_gallery_example", 725, 400)}}</p> + +<h2 id="控制图像的缩放行为_Controlling_image_scaling_behavior">控制图像的缩放行为 Controlling image scaling behavior</h2> + +<p>如同前文所述,过度缩放图像可能会导致图像模糊或像素化。您可以通过使用绘图环境的{{domxref("CanvasRenderingContext2D.imageSmoothingEnabled", "imageSmoothingEnabled")}}属性来控制是否在缩放图像时使用平滑算法。默认值为<code>true</code>,即启用平滑缩放。您也可以像这样禁用此功能:</p> + +<pre class="brush: js notranslate">ctx.mozImageSmoothingEnabled = false; +ctx.webkitImageSmoothingEnabled = false; +ctx.msImageSmoothingEnabled = false; +ctx.imageSmoothingEnabled = false;</pre> + +<p>{{PreviousNext("Web/API/Canvas_API/Tutorial/Drawing_text", "Web/API/Canvas_API/Tutorial/Transformations")}}</p> |