如何使 HTML5 画布适合动态父/弹性框容器

Posted

技术标签:

【中文标题】如何使 HTML5 画布适合动态父/弹性框容器【英文标题】:How to make a HTML5 canvas fit dynamic parent/flex box container 【发布时间】:2015-07-25 14:37:21 【问题描述】:

有没有办法可以在动态调整大小的 flex-box 容器中创建画布?

最好是纯 CSS 解决方案,但我认为重绘需要 javascript

我认为一种解决方案可能是监听 re-sizing 事件,然后缩放画布以满足 flex box 父级的大小,然后强制重绘,但我希望尽可能多地使用 CSS 或更多干净/更少的代码解决方案。

当前的方法是基于 CSS 的,画布根据父弹性框元素重新调整大小。在下面的屏幕截图中,图形被模糊、重新定位并从画布溢出。

CSS:

html,body
            margin:0;
            width:100%;
            height:100%;
        
        body
            display:flex;
            flex-direction:column;
        
header
            width:100%;
            height:40px;
            background-color:red;
        
        main
            display:flex;
            flex: 1 1 auto;
            border: 1px solid blue;
            width:80vw;
        
        canvas
            flex: 1 1 auto;
            background-color:black;
        

HTML:

<header>
</header>
<main>
    <canvas id="stage"></canvas>
</main>

Javascript:

$( document ).ready(function() 
    var ctx = $("#stage")[0].getContext("2d");
    ctx.strokeStyle="#FFFFFF";
    ctx.beginPath();
    ctx.arc(100,100,50,0,2*Math.PI);
    ctx.stroke();
);

显示问题的 JS 小提琴:https://jsfiddle.net/h2v1w0a1/

【问题讨论】:

canvas 正在占用其父级的宽度,即 80vw。 100vw 可以解决你的问题 no.. 这是有目的的,菜单稍后会在它的右侧。图形缩放是问题,可能需要进行编辑,以免其他人有相同的假设。 @Dima 你能澄清一下你所谓的“原始问题”吗?当我读到它时,当前接受的答案完美地回答了它(除了它可以从使用 ResizeObserver 的更新中获胜)。为了使画布“适合”在动态容器中,它必须调整大小,或者您是否正在考虑其他行为?如果有,是哪一个? 也许你是对的,也许我看不懂 :) 我想,最终,我希望有两种选择:画布跟随容器大小,改变和不改变画布(内部)像素大小。 我认为您正在寻找 object-fit 或纯粹的 CSS 解决方案。这是一个jsfiddle.net/vfsgpm3q。 【参考方案1】:

将画布视为图像。如果将其缩放到与原始大小不同的大小,它会在某些时候显得模糊,并且可能在早期取决于浏览器选择的插值算法。对于画布,浏览器倾向于选择双线性而不是双三次,因此与实际图像相比,模糊的风险更高。

您会希望画布中的内容尽可能清晰,唯一的方法是使用 JavaScript 调整大小以适应父级,并避免使用 CSS(后者适用于打印等内容,或者当您需要时) “视网膜分辨率”)。

要获取以像素为单位的父容器大小,您可以使用getComputedStyle()(请参阅下面的更新):

var parent = canvas.parentNode,
    styles = getComputedStyle(parent),
    w = parseInt(styles.getPropertyValue("width"), 10),
    h = parseInt(styles.getPropertyValue("height"), 10);

canvas.width = w;
canvas.height = h;

Fiddle

(注意:Chrome 目前似乎在计算 flex 方面存在一些问题)

更新

这里有一个更简单的方法:

var rect = canvas.parentNode.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;

Fiddle

【讨论】:

在画布上添加 style="position:absolute" 使它对我来说也适用于边缘。 第二个小提琴链接坏了。 @Glenn - 这也帮助了我使用 chrome。我已经获得了画布父级的大小并将画布设置为该大小,但是当我将那个父级放入弹性盒时,画布就会破裂。设置位置:画布上的绝对固定它。【参考方案2】:

我能想到的唯一CSS解决方案是使用object-fit:none(相关:How does object-fit work with canvas element?)

$(document).ready(function() 
  var ctx = $("#stage")[0].getContext("2d");
  ctx.strokeStyle = "#FFFFFF";
  ctx.beginPath();
  ctx.arc(100, 100, 50, 0, 2 * Math.PI);
  ctx.stroke();
);
html,
body 
  margin: 0;
  width: 100%;
  height: 100%;


body 
  display: flex;
  flex-direction: column;


header 
  width: 100%;
  height: 40px;
  background-color: red;


main 
  display: flex;
  flex: 1 1 auto;
  border: 1px solid blue;
  width: 80vw;


canvas 
  flex: 1 1 auto;
  background-color: black;
  object-fit:none;
  object-position:top left;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
</header>
<main>
  <canvas id="stage"></canvas>
</main>

【讨论】:

object-fit 如果设置为 none 肯定会起作用,但它不会调整画布大小。在这种情况下,Object-fit 设置为 contains 似乎比设置为 none 更合适。 @PeterDarmis 检查接受的答案的代码,您会看到目标是来调整画布内容的大小。包含和覆盖将调整内容的大小,这不是这里的目的 很可能我一定没有正确阅读问题中的这一部分... 我认为一种解决方案可能是收听重新调整大小的事件,然后缩放画布以满足大小flex box 父级然后强制重绘,但我希望使用尽可能多的 CSS 或更干净/更少的代码解决方案。 @PeterDarmis 缩放画布以满足弹性框父级的大小 --> 将画布的宽度/高度调整为父级之一,这将扭曲内容,这就是为什么:*然后强制重绘*->考虑到画布的新宽度/高度,我们再次重绘以获得准确的结果。查看问题的 jsfiddle(画布的内容被扭曲/更改)与内容保持不变的已接受答案的 jsfiddle 之间的区别。这就是我在这里所做的,这是使用 object-fit:none; 我不知道我什么时候回答我没有阅读其他答案或 cmets,我只阅读了问题并看到圆圈在提供的 jsfiddle 中被扭曲的事实。对我来说,这并不意味着问题不希望画布相应地调整大小。如果我在最初阅读该问题时错了,则不会将 case object-fit 设置为 none 起作用。但我真的对我上面评论中提到的那句话感到困扰。我真的认为它是要调整大小的,我还没有看到问题持有者来定义它。最好的问候,对不起我的评论。【参考方案3】:

使用object-fit 提供了一个纯粹的CSS 解决方案。我认为您需要自己检查在covercontain 之间将哪个值设置为object-fit 更好,下面有两个示例。

$(document).ready(function() 
  var ctx = $("#stage")[0].getContext("2d");
  ctx.strokeStyle = "#FFFFFF";
  ctx.beginPath();
  ctx.arc(100, 100, 50, 0, 2 * Math.PI);
  ctx.stroke();
);
html,
body 
  margin: 0;
  width: 100%;
  height: 100%;


body 
  display: flex;
  flex-direction: column;


header 
  width: 100%;
  height: 40px;
  background-color: red;


main 
  display: flex;
  flex: 1 1 auto;
  border: 1px solid blue;
  width: 80vw;


canvas 
  object-fit: cover;
  background-color: black;
  width: 100%;
  height: auto;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
</header>
<main>
  <canvas id="stage"></canvas>
</main>

$(document).ready(function() 
  var ctx = $("#stage")[0].getContext("2d");
  ctx.strokeStyle = "#FFFFFF";
  ctx.beginPath();
  ctx.arc(100, 100, 50, 0, 2 * Math.PI);
  ctx.stroke();
);
html,
body 
  margin: 0;
  width: 100%;
  height: 100%;


body 
  display: flex;
  flex-direction: column;


header 
  width: 100%;
  height: 40px;
  background-color: red;


main 
  display: flex;
  flex: 1 1 auto;
  border: 1px solid blue;
  width: 80vw;


canvas 
  object-fit: contain;
  background-color: black;
  width: 100%;
  height: auto;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
</header>
<main>
  <canvas id="stage"></canvas>
</main>

您还可以在 JSFiddle https://jsfiddle.net/vfsgpm3q/、https://jsfiddle.net/vfsgpm3q/1/ 中找到这两个示例

【讨论】:

【参考方案4】:

另一种解决方案是创建一个新的包含块,并使其遵循格式化上下文:

const Canvase = styled.canvas`
  flex: auto;
  position: relative;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
`;

它与 Temani 和 Peter 建议的 object-fit: ... 有点互补/正交

【讨论】:

以上是关于如何使 HTML5 画布适合动态父/弹性框容器的主要内容,如果未能解决你的问题,请参考以下文章

如何使弹性容器居中但左对齐弹性项目

如何使弹性容器居中但左对齐弹性项目

按比例调整图像大小以适合 HTML5 画布

如何在不给定高度的情况下使内容在弹性框中可滚动?

如何防止弹性项目在没有包装的情况下溢出弹性父项?

如何在不损失图像质量的情况下调整图像大小以适合小型 HTML5 画布? [复制]