• HTML5 canvas Web 绘图


    二维变形

    Canvas 绘图中另一个重要的概念是 绘画状态(Drawing State),绘画状态反映了渲染上下文当前的瞬时状态,开发人员可以通过对绘画状态的保存 / 恢复操作而快速的回到之前使用的各种属性和变形操作。绘画状态主要由以下三个部分构成:
        当前的变形矩阵(transformation matrix)
        当前的裁剪区域(clipping region)
        当前上下文中的属性,比如 strokeStyle, fillType, globalAlpha, font 等等。

    需要指出的是,当前路径对象以及当前的位图都不包含在绘画状态之中,路径是持续性的对象,如前文所讲,只有通过 beginPath() 操作才会进行重置,而位图则是 canvas 的属性,并非属于渲染上下文的。

    开发人员可以使用 save 和 restore 两种方法来保存和恢复 canvas 状态,每调用 save 方法,都会将当前状态压入堆栈中,而相应的 restore 方法则会从堆栈中弹出一个状态,并将当前画面恢复至该状态。绘画状态在 canvas 图形变形操作中应用极为广泛,也非常重要,因为调用一个 restore 方法远比手动恢复先前状态要简单许多,因而,一个较好的习惯是在做变形操作之前先保存 canvas 状态。

    二维绘图的常用变形操作在 canvas 中都可到了很好的支持,包括平移(Translate),旋转(Rotate),伸缩(Scale)等等。由于所有的变形操作都基于变形矩阵,因而开发人员始终需要记住一点的就是,一旦没有使用 save/restore 操作保持住原来的绘图状态,那么后续的绘图操作,都会在当前所应用的变形状态下完成。

    View Code
    function DrawPointCircle() {
    var canvas = document.getElementById('canvas');
    if (canvas.getContext) {
    var ctx = canvas.getContext('2d');
    ctx.translate(
    400,300); // 将 canvas 的原点从 (0,0) 平移至(400,300)
    for (i = 1; i <= 3; i++) { // 绘制内外 2 层
    if ((i % 2) == 1) { ctx.fillStyle = '#00f'; }
    else { ctx.fillStyle = '#f00'; }
    ctx.save();
    // 保持开始绘制每一层时的状态一致
    for (j = 0; j <= i * 6; j++) { // 每层生成点的数量
    ctx.rotate(Math.PI / (3 * i)); // 绕当前原点将坐标系顺时针旋转 Math.Pi/(3*i) 度
    ctx.beginPath();
    ctx.arc(
    0, 20 * i, 5, 0, Math.PI * 2, true);
    ctx.fill();
    // 使用 fillType 值填充每个点
    }
    ctx.restore();
    }
    }
    }

    像素级绘图

    像素级别的绘图操作是 canvas 绘图区别于 SVG,VML 等绘图技术的最为明显特征之一,渲染上下文提供了 createImageData, getImageData, 和 putImageData 三种方法来进行针对像素的操作,所基于的对象都是 imageData 对象。imageData 对象包含 width、height 和 data 三个属性,其中 data 包含了 width × height × 4 个像素值,之所以乘以 4,在于每个像素都有 RGB 值和透明度 alpha 值。

    清单 4 中所示代码为上一节中示例图形增添了简单的颜色反转滤镜效果,通过调用 getImageData(x,y,width,height) 方法获取以(x,y)为左上坐标的矩形区域内所有像素,而后对所有像素的 RGB 值做取反操作,最后通过 putImageData(imageData, x, y)将修改后的像素值重新绘制到在 canvas 上。

    View Code
    function revertImage(){
    var canvas = document.getElementById('canvas');
    if (canvas.getContext){
    var context = canvas.getContext('2d');
    // 从指定的矩形区域获取 canvas 像素数组
    var imgdata = context.getImageData(100, 100, 100, 100);
    var pixels = imgdata.data;

    // 遍历每个像素并对 RGB 值进行取反
    for (var i=0, n=pixels.length; i<n; i+= 4){
    pixels[i]
    = 255-pixels[i];
    pixels[i
    +1] = 255-pixels[i+1];
    pixels[i
    +2] = 255-pixels[i+2];
    }
    // 在指定位置进行像素重绘
    context.putImageData(imgdata, 100, 100);
    }
    }

    实现动画效果

    Canvas 并非为了制作动画而出现,自然没有动画制作中帧的概念。因而,使用定时器不断的重绘 canvas 画面成为了实现动画效果的通用解决方式。Javascript 中的 setInterval(code,millisec) 方法可以按照指定的时间间隔 millisec 来反复调用 code 所指向的函数或代码串,这样,通过将绘图函数作为第一个参数传给 setInterval 方法,在每次被调用的过程中移动画面中图形的位置,来最终达到一种动画的体验。需要注意的一点是,虽然 setinterval 方法的第二个参数允许开发人员对绘图函数的调用频率进行设定,但这始终都是一种最为理想的情况,由于这种绘图频率很大程度上取决于支持 canvas 的底层 JavaScript 引擎的渲染速度以及相应绘图函数的复杂性,因而实际运行的结果往往都是要慢于指定绘图频率的。

    清单 5 显示了一个小弹力球动画效果,在球没有到达四周边界时,绘图方法不断的移动所绘小球的横纵坐标。并且,在每次重绘之前,都是用 clear 方法将之前的画面清除。

    View Code
    <script type="text/javascript">
    var x=0,y=0,dx=2,dy=3,context2D; // 小球从(0,0)开始移动,横向步长为 2,纵向步长为 3

    function draw(){
    context2D.clearRect(
    0, 0, canvas.width, canvas.height); // 清除整个 canvas 画面
    drawCircle(x, y); // 使用自定义的画圆方法,在当前(x,y)坐标出画一个圆

    // 判断边界值,调整 dx/dy 以改变 x/y 坐标变化方向。
    if (x + dx > canvas.width || x + dx < 0) dx = -dx;
    if (y + dy > canvas.height || y + dy < 0) dy = -dy;
    x
    += dx;
    y
    += dy;
    }

    window.onload
    = function (){
    var canvas = document.getElementById('canvas');
    context2D
    = canvas.getContext('2d');
    setInterval(draw,
    20); // 设置绘图周期为 20 毫秒
    }
    </script>

    提高可访问性

    一款优秀的 Web 应用必须要做到的就是提供给用户很好的可访问性,这包括对鼠标,键盘以及快捷键等操作的响应,canvas 画面的本质仍是一个 DOM 节点,因而开发人员可以通过常规的方法来处理响应。这里,与基于 SVG 的绘图不同,由于 SVG 是一种基于 XML 的声明式的绘图方式,因而,SVG 中任何的图形都可以作为一个独立的 DOM 节点去接收并响应特定事件,而 canvas 由于其像素绘图的本质,则只可以在 canvas 元素节点去处理。

    图 5 所示示例代码,当鼠标在 canvas 中移动时,鼠标当前相对于 canvas 中的横纵坐标将实时输出到上方提示信息区域;当用户在 canvas 中单击鼠标左键,将在相应位置创建一个蓝色小球,而后用户可以通过键盘上的左 / 右方向键对蓝色小球进行控制,使其进行横向的移动。

    View Code

    <script type="text/javascript">
    var g_x,g_y; // 鼠标当前的坐标
    var g_pointx, g_pointy; // 蓝色小球当前的坐标
    var canvas;

    function drawCircle(x,y){ // 以鼠标当前位置为原点绘制一个蓝色小球
    var ctx = canvas.getContext('2d');
    ctx.clearRect(
    0,0,300,300);
    ctx.fillStyle
    = '#00f';
    ctx.beginPath();
    ctx.arc(x,y,
    20,0,Math.PI*2,true);
    ctx.fill();

    g_pointx
    = x;
    g_pointy
    = y
    }

    function onMouseMove(evt) {
    // 获取鼠标在 canvas 中的坐标位置
    if (evt.layerX || evt.layerX == 0) { // FireFox
    g_x = evt.layerX;
    g_y
    = evt.layerY;
    }
    document.getElementById(
    "xinfo").innerHTML = g_x;
    document.getElementById(
    "yinfo").innerHTML = g_y;
    }

    function onKeyPress(evt) {
    var dx = 3; // 横向平移步长
    var kbinfo = document.getElementById("kbinfo");

    if (evt.keyCode == 39){
    kbinfo.innerHTML
    ="right";
    if (g_x<300-dx) drawCircle(g_pointx+dx,g_pointy);
    document.getElementById(
    "xinfo").innerHTML = g_pointx;
    }
    else if (evt.keyCode == 37){
    kbinfo.innerHTML
    = "left";
    if (g_x>dx) drawCircle(g_pointx-dx,g_pointy);
    document.getElementById(
    "xinfo").innerHTML = g_pointx;
    }
    }

    window.onload
    = function(){
    canvas
    = document.getElementById('canvas');
    // 增加 canvas 节点对鼠标单击,移动以及键盘事件的响应函数
    canvas.addEventListener('click', function(evt){drawCircle(g_x, g_y);} , false);
    canvas.addEventListener(
    'mousemove', onMouseMove, false);
    canvas.addEventListener(
    'keypress', onKeyPress, false);
    canvas.focus();
    // 获得焦点之后,才能够对键盘事件进行捕获
    }
    </script>
  • 相关阅读:
    UVa 1451 Average (斜率优化)
    POJ 1160 Post Office (四边形不等式优化DP)
    HDU 3507 Print Article (斜率DP)
    LightOJ 1427 Substring Frequency (II) (AC自动机)
    UVa 10245 The Closest Pair Problem (分治)
    POJ 1741 Tree (树分治)
    HDU 3487 Play with Chain (Splay)
    POJ 2828 Buy Tickets (线段树)
    HDU 3723 Delta Wave (高精度+calelan数)
    UVa 1625 Color Length (DP)
  • 原文地址:https://www.cnblogs.com/xiaobuild/p/2104270.html
Copyright © 2020-2023  润新知