Which Way to Load a Huge Image (Canvas Vs Img Vs Background-Image)

Which way to load a huge image (canvas vs img vs background-image)?

Great question! This is related: When to use IMG vs. CSS background-image?

From that article, if people are intended to print your page you wouldn't use <img> - as this would appear on the print. The same would apply to <canvas>, making background-image the logical solution.

How is your background image added through CSS? Is it inline or through its own stylesheet? If it's using its own stylesheet, have you tried compressing the CSS before testing the speed?

This is also related, I suppose: Do Images Load Faster in HTML or CSS?

What's the difference between using img and canvas for drawing images?

You wouldn't, typically, use a canvas for displaying a single static image.

What you might do, is have a number of different image files, and compose on one canvas. For example, if you had images for "man" and "block" and "monster", and you rendered them onto a canvas 30 times a second, updating their relative positions based on, say, key events, then you've basically created a game.

If you just display a single static image, then there is no different from just using a <img> tag, and that is probably what you should do, because that makes it clearer what it is.

A <canvas> is for graphics you want to dynamically generate/manipulate using javascript.

Canvas vs. img - performance

No, img is faster, easier to create and more accessible than using canvas. The img tag was designed to show an image so if that's all you need to do, use an img tag. Multiple uses shouldn't matter as the image will only be downloaded once.

If you need some animation or interactivity, that is the time to consider canvas.

Loading large background images

I presume you're asking this question, because you found it surprising that the image didn't load "after" the page?

My favorite trick to prevent "FOUC" (Flash Of Unstyled ontent) is to base-64 encode such images into your CSS. It's essentially embedding your images in your CSS, and the browser won't render the page until the entire CSS file is downloaded.

This will cause your CSS file to be large, and IE8 has limitations on file size, but it's a really easy solution if you have a build process or a CSS preprocessor like LESS.

Do Images Load Faster in HTML or CSS?

This can easily be verified using Firebug (under Net), Chrome Developer Tools (under Network), Fiddler or any other HTTP sniffer you prefer.

If you put the image in CSS as background-image, the image will only get downloaded when that class is actually used and visible. If you put it in an img it'll be downloaded immediately and will block rendering even if it's invisible.

In the end they are both as fast if you are counting the speed at which the image loads per se. The real question is if the perceived performance is better as the order at which the image gets downloaded might be different depending on where you place your element.

I would worry more about the other aspects though. Having the image as a background-image means:

  • The image is not a content
  • It is not printable
  • You can use a sprite to reduce the number of HTTP requests (thus improving performance)
  • It is slower to animate background-image than img

Dealing with large images in HTML5 canvas

15000px x 15000px is indeed big.

The GPU would have to store it as raw RGB data in its memory (I don't remember the Maths exactly but I think it's something like width x height x 3 bytes, i.e 675MB in your case, which is more than most common GPUs can handle).

Add to that all the other graphics you might have, and your GPU will be forced to drop your big image and to grab it again every frame.

In order to avoid that, you'd probably be better to split your big image into multiple smaller ones, and to call multiple times drawImage per frame. This way, in worst case, the GPU will only have to fetch the part needed, and in best case, it will already have it in its memory.

Here is a rough proof of concept, which will split a 5000*5000px svg image in tiles of 250*250px. Of course, you will have to adapt it for your needs, but it might give you an idea.

console.log('generating image...');var bigImg = new Image();bigImg.src = URL.createObjectURL(generateBigImage(5000, 5000));bigImg.onload = init;
function splitBigImage(img, maxSize) { if (!maxSize || typeof maxSize !== 'number') maxSize = 500;
var iw = img.naturalWidth, ih = img.naturalHeight, tw = Math.min(maxSize, iw), th = Math.min(maxSize, ih), tileCols = Math.ceil(iw / tw), // how many columns we'll have tileRows = Math.ceil(ih / th), // how many rows we'll have tiles = [], r, c, canvas;
// draw every part of our image once on different canvases for (r = 0; r < tileRows; r++) { for (c = 0; c < tileCols; c++) { canvas = document.createElement('canvas'); // add a 1px margin all around for antialiasing when drawing at non integer canvas.width = tw + 2; canvas.height = th + 2; canvas.getContext('2d') .drawImage(img, (c * tw | 0) - 1, // compensate the 1px margin (r * tw | 0) - 1, iw, ih, 0, 0, iw, ih); tiles.push(canvas); } }
return { width: iw, height: ih, // the drawing function, takes the output context and x,y positions draw: function drawBackground(ctx, x, y) { var cw = ctx.canvas.width, ch = ctx.canvas.height; // get our visible rectangle as rows and columns indexes var firstRowIndex = Math.max(Math.floor((y - th) / th), 0), lastRowIndex = Math.min(Math.ceil((ch + y) / th), tileRows), firstColIndex = Math.max(Math.floor((x - tw) / tw), 0), lastColIndex = Math.min(Math.ceil((cw + x) / tw), tileCols);
var col, row; // loop through visible tiles and draw them for (row = firstRowIndex; row < lastRowIndex; row++) { for (col = firstColIndex; col < lastColIndex; col++) { ctx.drawImage( tiles[row * tileCols + col], // which part col * tw - x - 1, // x position row * th - y - 1 // y position ); } } } };}
function init() { console.log('image loaded');
var bg = splitBigImage(bigImg, 250); // image_source, maxSize var ctx = document.getElementById('canvas').getContext('2d'); var dx = 1, dy = 1, x = 150, y = 150; anim(); setInterval(changeDirection, 2000);
function anim() { // just to make the background position move... x += dx; y += dy; if (x < 0) { dx *= -1; x = 1; } if (x > bg.width - ctx.canvas.width) { dx *= -1; x = bg.width - ctx.canvas.width - 1; } if (y < 0) { dy *= -1; y = 1; } if (y > bg.height - ctx.canvas.height) { dy *= -1; y = bg.height - ctx.canvas.height - 1; } ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); if(chck.checked) { // that's how you call it bg.draw(ctx, x, y); } else { ctx.drawImage(bigImg, -x, -y); } requestAnimationFrame(anim); }
function changeDirection() { dx = (Math.random()) * 5 * Math.sign(dx); dy = (Math.random()) * 5 * Math.sign(dy); }
setTimeout(function() { console.clear(); }, 1000);}// produces a width * height pseudo-random svg imagefunction generateBigImage(width, height) { var str = '<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg">', x, y; for (y = 0; y < height / 20; y++) for (x = 0; x < width / 20; x++) str += '<circle ' + 'cx="' + ((x * 20) + 10) + '" ' + 'cy="' + ((y * 20) + 10) + '" ' + 'r="15" ' + 'fill="hsl(' + (width * height / ((y * x) + width)) + 'deg, ' + (((width + height) / (x + y)) + 35) + '%, 50%)" ' + '/>'; str += '</svg>'; return new Blob([str], { type: 'image/svg+xml' });}
<label>draw split <input type="checkbox" id="chck" checked></label><canvas id="canvas" width="800" height="800"></canvas>


Related Topics



Leave a reply



Submit