【问题标题】:Adjust canvas image size like 'background-size: cover' and responsive调整画布图像大小,如“背景大小:封面”和响应式
【发布时间】:2021-03-10 08:10:15
【问题描述】:

我想做什么

  • 在画布图像上设置background-size:cover-like 效果
  • 在调整窗口大小(响应式)时重新绘制画布图像

我尝试了什么

我尝试了以下两种方法。两者都不起作用。

  1. Simulation background-size: cover in canvas
  2. How to set background size cover on a canvas

问题

  • 图片的纵横比不固定。
  • 我不确定,但图像似乎没有在调整窗口大小时重新渲染。

我的代码

function draw() {
  // Get <canvas>
  const canvas = document.querySelector('#canvas');

  // Canvas
  const ctx = canvas.getContext('2d');
  const cw = canvas.width;
  const ch = canvas.height;

  // Image
  const img = new Image();
  img.src = 'https://source.unsplash.com/WLUHO9A_xik/1600x900';

  img.onload = function() {
    const iw = img.width;
    const ih = img.height;

    // 'background-size:cover'-like effect
    const aspectHeight = ih * (cw / iw);
    const heightOffset = ((aspectHeight - ch) / 2) * -1;
    ctx.drawImage(img, 0, heightOffset, cw, aspectHeight);
  };
}

window.addEventListener('load', draw);
window.addEventListener('resize', draw);
canvas {
  display: block;
  /* canvas width depends on parent/window width */
  width: 90%;
  height: 300px;
  margin: 0 auto;
  border: 1px solid #ddd;
}
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【问题讨论】:

    标签: javascript image canvas


    【解决方案1】:

    画布width / height 属性不反映它的实际大小与使用CSS 调整大小的关系。给定您的代码,cw/ch 固定为300/150。所以整个计算都是基于不正确的值。

    您需要使用实际反映其可见尺寸的值。喜欢clientWidth / clientHeight

    一个非常简单的解决方案是在使用它们进行任何计算之前更新画布宽度/高度属性。例如:

    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
    

    完整示例:

    const canvas = document.querySelector('#canvas');
    const ctx = canvas.getContext('2d');
    
    // only load the image once
    const img = new Promise(r => {
      const img = new Image();
    
      img.src = 'https://source.unsplash.com/WLUHO9A_xik/1600x900';
      img.onload = () => r(img);
    });
    
    const draw = async () => {
      // resize the canvas to match it's visible size
      canvas.width  = canvas.clientWidth;
      canvas.height = canvas.clientHeight;
    
      const loaded = await img;
      const iw     = loaded.width;
      const ih     = loaded.height;
      const cw     = canvas.width;
      const ch     = canvas.height;
      const f      = Math.max(cw/iw, ch/ih);
    
      ctx.setTransform(
        /*     scale x */ f,
        /*      skew x */ 0,
        /*      skew y */ 0,
        /*     scale y */ f,
        /* translate x */ (cw - f * iw) / 2,
        /* translate y */ (ch - f * ih) / 2,
      );
    
      ctx.drawImage(loaded, 0, 0);
    };
    
    window.addEventListener('load', draw);
    window.addEventListener('resize', draw);
    .--dimensions {
        width: 90%;
        height: 300px;
    }
    
    .--border {
        border: 3px solid #333;
    }
    
    canvas {
        display: block;
        margin: 0 auto;
    }
    
    #test {
        background: url(https://source.unsplash.com/WLUHO9A_xik/1600x900) no-repeat center center;
        background-size: cover;
        margin: 1rem auto 0;
    }
    <canvas id="canvas" class="--dimensions --border"></canvas>
    <div id="test" class="--dimensions --border"></div>

    【讨论】:

    • 这段代码很完美!它非常简单和简短。而且和background-size: cover完全一样。感谢您展示比较。
    【解决方案2】:

    首先,只加载一次图像,目前每次调整页面大小时都重新加载。

    然后,您的变量 cwch 将始终为 300150,因为您没有设置画布的大小。请记住,the canvas has two different sizes,它的布局一(由 CSS 控制)和它的缓冲区一(由其 .width.height 属性控制)。
    您可以通过元素的.offsetWidth.offsetHeight 属性检索布局值。

    最后,您的代码会进行 contain 图像大小调整。要进行封面,您可以参考您链接到的答案,尤其是K3N's one

    {
      // Get <canvas>
      const canvas = document.querySelector('#canvas');
      // Canvas
      const ctx = canvas.getContext('2d');
      // Image
      const img = new Image();
      img.src = 'https://source.unsplash.com/WLUHO9A_xik/1600x900';
    
      function draw() {
        // get the correct dimension as calculated by CSS
        // and set the canvas' buffer to this dimension
        const cw = canvas.width = canvas.offsetWidth;
        const ch = canvas.height = canvas.offsetHeight;
    
        if( !inp.checked ) {
          drawImageProp(ctx, img, 0, 0, cw, ch, 0, 0);
        }
      }
    
      img.onload = () => {
        window.addEventListener('resize', draw);
        draw();
      };
    
      inp.oninput = draw;
    }
    
    // by Ken Fyrstenberg https://stackoverflow.com/a/21961894/3702797
    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);
    }
    canvas {
      display: block;
      /* canvas width depends on parent/window width */
      width: 90%;
      height: 300px;
      margin: 0 auto;
      border: 1px solid #ddd;
      background-image: url('https://source.unsplash.com/WLUHO9A_xik/1600x900');
      background-size: cover;
      background-repeat: no-repeat;
    }
    <label><input id="inp" type="checkbox">show CSS rendering</label>
    <canvas id="canvas"></canvas>

    【讨论】:

    • 感谢您的友好解释。这很容易理解。现在多亏了你,我明白为什么我的代码不起作用了。
    猜你喜欢
    • 2021-11-02
    • 2016-08-25
    • 2014-01-07
    • 2017-11-15
    • 2013-12-17
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 2016-07-03
    相关资源
    最近更新 更多