【问题标题】:Duplicating a canvas many times: clone the canvas or copy the image data?多次复制画布:克隆画布还是复制图像数据?
【发布时间】:2015-07-18 22:05:20
【问题描述】:

我的一个界面元素正在使用 HTML5 <canvas> 元素和相关的 JavaScript API 呈现。此元素用于同一屏幕上的多个位置以及整个应用程序的多个屏幕上。在需要的任何地方显示此内容的最有效方法是什么?

我的第一个想法是绘制到主画布,然后将其克隆并插入到页面中需要的位置。主画布可能类似于:

var master = $('<canvas>').attr({
      width: 100,
      height: 100
    }),
    c = master[0],
    ctx = c.getContext("2d");

    ctx.fillStyle = "#FF0000";
    ctx.fillRect(0, 0, 150, 75);

假设我想在这些 div 容器中复制画布:

<div class="square-container" id="square_header"></div>
...
<div class="square-container" id="square_dataTable"></div>
...
<div class="square-container" id="square_gallery"></div>
....

当页面加载时,我将在每个容器中插入一个重复的画布元素:

$(document).ready(function() {
    $('.square-container').each(function() {
        master.clone().appendTo($(this));
    });
}); 

在画布上呈现的内容将比本示例中使用的简单正方形更复杂,但最终仍将只是静态图像。不过,也有可能有几十张不同的图片,每张图片每页克隆了几十次。

我想到的另一种方法是使用toDataURL() 方法创建图像并将其设置为适当的图像源:

var master = $('<canvas>').attr({
    width: 100,
    height: 100
}),
    c = master[0],
    ctx = c.getContext("2d");

    ctx.fillStyle = "#FF0000";
    ctx.fillRect(0,0,150,75);

var square = c.toDataURL('image/png'); 

我会在必要时添加图片标签:

<img src="" id="square_header" class="square" alt="" />
...
<img src="" id="square_dataTable1" class="square" alt="" />
...
<img src="" id="square_gallery" class="square" alt="" />
....

然后将他们所有的 SRC 设置为新创建的图像:

$(document).ready(function() {
    $('img.square').attr('src', square);
});

对我来说,它看起来很像一个六个,另一个六个。但我想知道一种方法是否被认为比另一种更好?如果在&lt;canvas&gt; 上呈现的内容更复杂,那么一种方式会比另一种更有效吗?

本着同样的精神,当我需要在后续页面上使用该元素时,最好在每个页面上执行所有 javascript(从上面认为最好的任何解决方案)还是将 CANVAS_ELEMENT.toDataURL() 的值保存在cookie,然后在后续页面上使用它会更有效吗?

【问题讨论】:

    标签: javascript html canvas cloning


    【解决方案1】:

    克隆画布将复制其尺寸和样式,但不会复制其图像数据。您可以通过在上下文中调用drawImage 来复制图像数据。要将originalCanvas 的内容绘制到duplicateCanvas 上,请编写:

    duplicateCanvas.getContext('2d').drawImage(originalCanvas, 0, 0);
    

    作为演示,下面的 sn -p 生成四个画布:

    • 一个画有小场景的原始画布

    • 仅通过调用cloneNode 制作的副本

    • 通过调用cloneNodedrawImage制作的副本

    • 通过创建新图像并将其源设置为数据 URI 来制作副本

    function message(s) {
      document.getElementById('message').innerHTML += s + '<br />';
    }
    
    function timeIt(action, description, initializer) {
      var totalTime = 0,
          initializer = initializer || function () {};
      initializer();
      var startTime = performance.now();
      action();
      var elapsed = performance.now() - startTime;
      message('<span class="time"><span class="number">' +
          Math.round(elapsed * 1000) + ' &mu;s</span></span> ' + description);
    }
    
    function makeCanvas() {
      var canvas = document.createElement('canvas'),
          context = canvas.getContext('2d');
      canvas.width = 100;
      canvas.height = 100;
      timeIt(function () {
        context.fillStyle = '#a63d3d';
        context.fillRect(10, 10, 80, 40);   // Paint a small scene.
        context.fillStyle = '#3b618c';
        context.beginPath();
        context.arc(60, 60, 25, 0, 2*Math.PI);
        context.closePath();
        context.fill();
      }, '(millionths of a second) to draw original scene', function () {
        context.clearRect(0, 0, canvas.width, canvas.height);
      });
      return canvas;
    }
    
    // copyCanvas returns a canvas containing the same image as the given canvas.
    function copyCanvas(original) {
      var copy;
      timeIt(function () {
        copy = original.cloneNode();  // Copy the canvas dimensions.
        copy.getContext('2d').drawImage(original, 0, 0);  // Copy the image.
      }, 'to copy canvas with cloneNode and drawImage');
      return copy;
    }
    
    // imageFromStorage extracts the image data from a canvas, stores the image data
    // in a browser session, then retrieves the image data from the session and
    // makes a new image element out of it. We measure the total time to retrieve
    // the data and make the image.
    function imageFromStorage(original) {
      var image,
          dataURI = original.toDataURL();
      timeIt(function () {
        image = document.createElement('img');
        image.src = dataURI;
      }, 'to make image from a dataURI');
      return image;
    }
    
    function pageLoad() {
      var target = document.getElementById('canvases'),
          containers = {},  // We'll put the canvases inside divs.
          names = ['original', 'cloneNode', 'drawImage', 'dataURI'];
      for (var i = 0; i < names.length; ++i) {
        var name = names[i],  // Use the name as an ID and a visible header.
            container = document.createElement('div'),
            header = document.createElement('div');
        container.className = 'container';
        header.className = 'header';
        header.innerHTML = container.id = name;
        container.appendChild(header);
        target.appendChild(container);
        containers[name] = container;  // The canvas container is ready.
      }
      var canvas = makeCanvas();
      containers.original.appendChild(canvas);  // Original canvas.
      containers.cloneNode.appendChild(canvas.cloneNode());  // cloneNode
      containers.drawImage.appendChild(copyCanvas(canvas));  // cloneNode + drawImage
      containers.dataURI.appendChild(imageFromStorage(canvas));  // localStorage
    }
    
    pageLoad();
    body {
      font-family: sans-serif;
    }
    .header {
      font-size: 18px;
    }
    .container {
      margin: 10px;
      display: inline-block;
    }
    canvas, img {
      border: 1px solid #eee;
    }
    #message {
      color: #666;
      font-size: 16px;
      line-height: 28px;
    }
    #message .time {
      display: inline-block;
      text-align: right;
      width: 100px;
    }
    #message .number {
      font-weight: bold;
      padding: 1px 3px;
      color: #222;
      background: #efedd4;
    }
    <div id="canvases"></div>
    
    <div id="message"></div>

    如果您调用toDataURL 将图像数据复制到一个字符串中以供其他页面使用,请不要将该字符串放入cookie 中。 Cookie 旨在存储少量数据。相反,使用 HTML5 Web Storage API 将图像数据存储在浏览器中。或者,如果图像在用户会话之间没有变化,您可以将其呈现为服务器上的 PNG 图像,并使用 Cache-Control 标头鼓励浏览器使用cache the image file for fast retrieval

    就客户端图像渲染的性能而言,重新绘制场景可能比将字符串化的图像数据绘制到画布上更快。解码字符串并绘制像素是一项相对昂贵的操作。要了解在每个页面上重绘场景是否有意义,您可以使用performance.now 为您的绘图操作计时,如 sn-p 中所示。

    【讨论】:

    • 太棒了!感谢您提供详细的答案/解释...尤其是使用 drawImage() 方法的提示-我相当肯定我会一直撞到墙上试图弄清楚“为什么我的克隆 &lt;canvas&gt; 不是显示?”。
    • >如果图像在用户会话之间没有变化,您也可以将其渲染为服务器上的 PNG 图像。我喜欢这个想法,不幸的是它需要额外的资源(后端人员的时间之一)来实现。对于静态的、简单的(尽管可能很多)图像,将图像文件渲染到服务器是否有足够大的潜在性能优势,而不是在我应该推送的网络存储中存储数据字符串?
    • 如果浏览器缓存图像,您将获得性能优势,服务器可以通过 Cache-Control 标头鼓励这一点。从浏览器缓存中检索图像文件非常快。问题是图像是否可以提前渲染。如果您必须坚持使用客户端图像渲染,那么评估复制画布的各种方法是有意义的。 Stack Overflow sn-ps 无法使用本地存储,所以我做了一个external demo。我发现从本地存储制作图像最慢,尽管仍然不到一毫秒。
    猜你喜欢
    • 1970-01-01
    • 2018-08-14
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 2015-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多