Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulation background-size: cover in canvas

People also ask

What is cover background-size?

Pretty sure background-size: cover; means the image will fill the element while maintaining its aspect ratio, while background-size: 100%; will just make the image fill 100% width of the element.

What is difference between cover and contain in background-size?

cover tells the browser to make sure the image always covers the entire container, even if it has to stretch the image or cut a little bit off one of the edges. contain , on the other hand, says to always show the whole image, even if that leaves a little space to the sides or bottom.

How do I change the background image in canvas?

If you want to draw an image to the canvas, create an Image object with the source set to the required image's URL, and then simply use context. drawImage . If you draw the image before anything else in drawWave , right when you clear the canvas, you have your background.


It's a bit more complicated to get a cover functionality, though here is one solution for this:

Demo

Updated 2016-04-03 to address special cases. Also see @Yousef's comment below.

/**
 * By Ken Fyrstenberg Nilsen
 *
 * drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
 *
 * If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {

    if (arguments.length === 2) {
        x = y = 0;
        w = ctx.canvas.width;
        h = ctx.canvas.height;
    }

    // default offset is center
    offsetX = typeof offsetX === "number" ? offsetX : 0.5;
    offsetY = typeof offsetY === "number" ? offsetY : 0.5;

    // keep bounds [0.0, 1.0]
    if (offsetX < 0) offsetX = 0;
    if (offsetY < 0) offsetY = 0;
    if (offsetX > 1) offsetX = 1;
    if (offsetY > 1) offsetY = 1;

    var iw = img.width,
        ih = img.height,
        r = Math.min(w / iw, h / ih),
        nw = iw * r,   // new prop. width
        nh = ih * r,   // new prop. height
        cx, cy, cw, ch, ar = 1;

    // decide which gap to fill    
    if (nw < w) ar = w / nw;                             
    if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh;  // updated
    nw *= ar;
    nh *= ar;

    // calc source rectangle
    cw = iw / (nw / w);
    ch = ih / (nh / h);

    cx = (iw - cw) * offsetX;
    cy = (ih - ch) * offsetY;

    // make sure source rectangle is valid
    if (cx < 0) cx = 0;
    if (cy < 0) cy = 0;
    if (cw > iw) cw = iw;
    if (ch > ih) ch = ih;

    // fill image in dest. rectangle
    ctx.drawImage(img, cx, cy, cw, ch,  x, y, w, h);
}

Now you can call it like this:

drawImageProp(ctx, image, 0, 0, width, height);

and it will scale the image proportionally to fit inside in that container.

Use the two last parameters to offset the image:

var offsetX = 0.5;   // center x
var offsetY = 0.5;   // center y
drawImageProp(ctx, image, 0, 0, width, height, offsetX, offsetY);

Hope this helps!


If you're looking for a simpler solution that will work for most cases, and also includes css contain-like functionality, try this:

function fit(contains) {
  return (parentWidth, parentHeight, childWidth, childHeight, scale = 1, offsetX = 0.5, offsetY = 0.5) => {
    const childRatio = childWidth / childHeight
    const parentRatio = parentWidth / parentHeight
    let width = parentWidth * scale
    let height = parentHeight * scale

    if (contains ? (childRatio > parentRatio) : (childRatio < parentRatio)) {
      height = width / childRatio
    } else {
      width = height * childRatio
    }

    return {
      width,
      height,
      offsetX: (parentWidth - width) * offsetX,
      offsetY: (parentHeight - height) * offsetY
    }
  }
}

export const contain = fit(true)
export const cover = fit(false)

slightly modified version of intrinsic-scale to include scale and offset

Usage:

import {cover, contain} from './intrinsic-scale'

const {
  offsetX, 
  offsetY, 
  width, 
  height
} = cover(parentWidth, parentHeight, imageWidth, imageHeight)

// or...

const {
  offsetX, 
  offsetY, 
  width, 
  height
} = contain(parentWidth, parentHeight, imageWidth, imageHeight)

ctx.drawImage(image, offsetX, offsetY, width, height)

Canvas image fitting canvas like background-size cover and contain

const coverImg = (img, type) => {
  const imgRatio = img.height / img.width
  const winRatio = window.innerHeight / window.innerWidth
  if ((imgRatio < winRatio && type === 'contain') || (imgRatio > winRatio && type === 'cover')) {
    const h = window.innerWidth * imgRatio
    ctx.drawImage(img, 0, (window.innerHeight - h) / 2, window.innerWidth, h)
  }
  if ((imgRatio > winRatio && type === 'contain') || (imgRatio < winRatio && type === 'cover')) {
    const w = window.innerWidth * winRatio / imgRatio
    ctx.drawImage(img, (win.w - w) / 2, 0, w, window.innerHeight)
  }
}

Codepen demo

Usage:

coverImg(myImage, 'cover');
coverImg(myImage, 'contain');