如何使 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 中被扭曲的事实。对我来说,这并不意味着问题不希望画布相应地调整大小。如果我在最初阅读该问题时错了,则不会将 caseobject-fit
设置为 none 起作用。但我真的对我上面评论中提到的那句话感到困扰。我真的认为它是要调整大小的,我还没有看到问题持有者来定义它。最好的问候,对不起我的评论。【参考方案3】:
使用object-fit
提供了一个纯粹的CSS
解决方案。我认为您需要自己检查在cover
和contain
之间将哪个值设置为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 画布适合动态父/弹性框容器的主要内容,如果未能解决你的问题,请参考以下文章