javascript - ปรับขนาดแคนวาส HTML5 ให้พอดีกับหน้าต่าง
ฉันจะปรับขนาดองค์ประกอบ HTML5 <canvas> ให้พอดีกับหน้าต่างโดยอัตโนมัติได้อย่างไร?
ตัวอย่างเช่น ฉันสามารถทำให้ <div> ขยายขนาดได้โดยการตั้งค่าคุณสมบัติ height และ width เป็น 100% แต่ <canvas> จะไม่สามารถขยายขนาดได้ใช่ไหม?
คำตอบ #1
ผมเชื่อว่าผมได้พบวิธีแก้ปัญหาที่สง่างามสำหรับเรื่องนี้แล้ว:
จาวาสคริปต์
/* important! for alignment, you should make things
* relative to the canvas' current width/height.
*/
function draw() {
var ctx = (a canvas context);
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
//...drawing code...
}
ซีเอสเอส
html, body {
width: 100%;
height: 100%;
margin: 0;
}
จนถึงตอนนี้ ยังไม่มีผลกระทบด้านประสิทธิภาพเชิงลบขนาดใหญ่ต่อฉันเลย
คำตอบ #2
วิธีแก้ปัญหาต่อไปนี้ใช้ได้ผลดีที่สุดสำหรับฉัน เนื่องจากฉันค่อนข้างใหม่ต่อการเขียนโค้ด ฉันชอบที่จะมีการยืนยันทางสายตาว่าสิ่งใดกำลังทำงานตามที่ฉันคาดไว้ ฉันพบมันที่เว็บไซต์ดังต่อไปนี้: http://htmlcheats.com/html/resize-the-html5-canvas-dyamically/
นี่คือโค้ด:
(function() {
var
// Obtain a reference to the canvas element using its id.
htmlCanvas = document.getElementById('c'),
// Obtain a graphics context on the canvas element for drawing.
context = htmlCanvas.getContext('2d');
// Start listening to resize events and draw canvas.
initialize();
function initialize() {
// Register an event listener to call the resizeCanvas() function
// each time the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas. In this case it's a blue, 5 pixel
// border that resizes along with the browser window.
function redraw() {
context.strokeStyle = 'blue';
context.lineWidth = '5';
context.strokeRect(0, 0, window.innerWidth, window.innerHeight);
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas() {
htmlCanvas.width = window.innerWidth;
htmlCanvas.height = window.innerHeight;
redraw();
}
})();html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
/* Disable scrollbars */
display: block;
/* No floating content on sides */
}<canvas id='c' style='position:absolute; left:0px; top:0px;'></canvas>ขอบสีน้ำเงินจะแสดงขอบของผืนผ้าใบที่ปรับขนาดได้ และจะอยู่ตามขอบของหน้าต่างเสมอ มองเห็นได้ทั้ง 4 ด้าน ซึ่งไม่ได้เกิดขึ้นกับคำตอบอื่น ๆ ข้างต้นบางคำตอบ หวังว่าจะช่วยได้นะ
คำตอบ #3
คำตอบปี 2022
วิธีที่แนะนำในปี 2022 ในการตรวจสอบว่าองค์ประกอบมีการปรับขนาดหรือไม่คือการใช้ ResizeObserver
const observer = new ResizeObserver(myResizeTheCanvasFn);
observer.observe(someCanvasElement);
มันดีกว่า window.addEventListener('resize', myResizeTheCanvasFn) หรือ onresize=myResizeTheCanvasFn เพราะมันจัดการทุกกรณีของการปรับขนาดแคนวาส แม้กระทั่งเมื่อไม่เกี่ยวข้องกับการปรับขนาดหน้าต่าง
เช่นเดียวกัน มันไม่มีความหมายที่จะใช้ window.innerWidth, window.innerHeight คุณต้องการขนาดของแคนวาสเอง ไม่ใช่ขนาดของหน้าต่าง ด้วยวิธีนี้ ไม่ว่าคุณจะวางแคนวาสไว้ที่ไหน คุณก็จะได้ขนาดที่ถูกต้องตามสถานการณ์และไม่จำเป็นต้องเขียนโค้ดการปรับขนาดใหม่
เกี่ยวกับการทำให้ผ้าใบเติมเต็มหน้าต่าง
html, body {
height: 100%;
}
canvas {
width: 100%;
height: 100%;
display: block; /* this is IMPORTANT! */
}
เหตุผลที่คุณต้องใช้ display: block เพราะโดยค่าเริ่มต้นแคนวาสเป็น inline ซึ่งหมายความว่าจะรวมพื้นที่ว่างเพิ่มเติมที่ปลาย หากไม่ใช้ display: block คุณจะได้แถบเลื่อน หลายคนแก้ปัญหาแถบเลื่อนโดยการเพิ่ม overflow: hidden ใน body ของเอกสาร แต่สิ่งนั้นเป็นเพียงการซ่อนความจริงที่ว่า CSS ของแคนวาสไม่ได้ถูกตั้งค่าอย่างถูกต้อง ควรแก้ไขบั๊ก (ตั้งค่าแคนวาสเป็น display: block) ดีกว่าซ่อนบั๊กด้วย overflow: hidden
ตัวอย่างเต็ม
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
let width = 300;
let height = 150;
const observer = new ResizeObserver((entries) => {
width = canvas.clientWidth;
height = canvas.clientHeight;
});
observer.observe(canvas)
// not import but draw something just to showcase
const hsla = (h, s, l, a) => `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, ${a})`;
function render(time) {
canvas.width = width;
canvas.height = height;
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate(time * 0.0001);
const range = Math.max(width, height) * 0.8;
const size = 64 + Math.sin(time * 0.001) * 50;
for (let i = 0; i < range; i += size) {
ctx.fillStyle = hsla(i / range * 0.3 + time * 0.0001, 1, 0.5, 1);
ctx.fillRect( i, -range, size, range * 2);
ctx.fillRect(-i, -range, size, range * 2);
}
ctx.restore();
requestAnimationFrame(render)
}
requestAnimationFrame(render)html, body {
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
}<canvas></canvas>คุณอาจถามว่า ทำไมเราต้องบันทึกความกว้างและความสูงใน callback ของ ResizeObserver แทนที่จะตั้งค่าขนาดของ canvas โดยตรง เหตุผลก็คือ สเปคของ HTML5 ระบุว่า callback ของ ResizeObserver จะเกิดขึ้นหลังจาก callback ของ requestAnimationFrame แต่ก่อนการคอมโพสิต การตั้งค่าความกว้างหรือความสูงของ canvas จะล้าง canvas ทั้งหมด รวมกันแล้วหมายความว่า หากผู้ใช้ปรับขนาด ลำดับของการทำงานจะเป็น
- วาด (requestAnimationFrame)
- ล้าง (ResizeObserver)
- ประกอบ
ซึ่งจะจบลงด้วยการกระพริบ คุณสามารถเห็นได้ในตัวอย่างนี้ที่กำหนดขนาดของแคนวาสโดยตรงภายใน resizeobserver เรียกใช้มัน เลือก "เต็มหน้า" แล้วปรับขนาดหน้าต่างเบราว์เซอร์
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const observer = new ResizeObserver((entries) => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
});
observer.observe(canvas)
// not import but draw something just to showcase
const hsla = (h, s, l, a) => `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, ${a})`;
function render(time) {
const {width, height} = canvas;
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate(time * 0.0001);
const range = Math.max(width, height) * 0.8;
const size = 64 + Math.sin(time * 0.001) * 50;
for (let i = 0; i < range; i += size) {
ctx.fillStyle = hsla(i / range * 0.3 + time * 0.0001, 1, 0.5, 1);
ctx.fillRect( i, -range, size, range * 2);
ctx.fillRect(-i, -range, size, range * 2);
}
ctx.restore();
requestAnimationFrame(render)
}
requestAnimationFrame(render)html, body {
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
}<canvas></canvas>หากคุณต้องจัดการหลายผืนผ้าใบ คุณจะต้องมีวิธีเก็บขนาดของแต่ละผืน วิธีหนึ่งคือใช้ WeakMap
const canvases = document.querySelectorAll('canvas');
const canvasToSize = new WeakMap();
function recordCanvasSize(canvas) {
canvasToSize.set(canvas, {
width: canvas.clientWidth,
height: canvas.clientHeight,
});
}
const observer = new ResizeObserver((entries) => {
entries.forEach(entry => recordCanvasSize(entry.target));
});
canvases.forEach(canvas => {
recordCanvasSize(canvas);
observer.observe(canvas)
});
// not import but draw something just to showcase
const hsla = (h, s, l, a) => `hsla(${h * 360}, ${s * 100}%, ${l * 100}%, ${a})`;
function render(time) {
canvases.forEach((canvas, n) => {
const {width, height} = canvasToSize.get(canvas);
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.rotate(time * 0.0001 + n);
const range = Math.max(width, height) * 0.8;
const size = 64 + Math.sin(time * 0.001 + n) * 50;
for (let i = 0; i < range; i += size) {
ctx.fillStyle = hsla(i / range * 0.3 + time * 0.0001 + n / 3, 1, 0.5, 1);
ctx.fillRect( i, -range, size, range * 2);
ctx.fillRect(-i, -range, size, range * 2);
}
ctx.restore();
});
requestAnimationFrame(render)
}
requestAnimationFrame(render)html, body {
height: 100%;
margin: 0;
}
#outer {
height: 100%;
display: flex;
align-items: stretch;
}
canvas {
flex: 1 1 auto;
display: block;
width: 100%;
min-width: 0;
}<div id="outer">
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
</div>หมายเหตุ: มีปัญหาอื่น ๆ ที่เกี่ยวข้องกับการปรับขนาดแคนวาส โดยเฉพาะถ้าคุณต้องการจัดการกับการตั้งค่า devicePixelRatio ที่แตกต่างกัน ดูบทความนี้สำหรับข้อมูลเพิ่มเติม
คำตอบ #4
พื้นฐานสิ่งที่คุณต้องทำคือการผูกเหตุการณ์ onresize กับ body ของคุณ เมื่อคุณจับเหตุการณ์ได้ คุณเพียงแค่ต้องปรับขนาดแคนวาสโดยใช้ window.innerWidth และ window.innerHeight
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Canvas Resize</title>
<script type="text/javascript">
function resize_canvas(){
canvas = document.getElementById("canvas");
if (canvas.width < window.innerWidth)
{
canvas.width = window.innerWidth;
}
if (canvas.height < window.innerHeight)
{
canvas.height = window.innerHeight;
}
}
</script>
</head>
<body onresize="resize_canvas()">
<canvas id="canvas">Your browser doesn't support canvas</canvas>
</body>
</html>
คำตอบ #5
การตั้งค่าความกว้างและความสูงของพื้นที่พิกัดของผ้าใบตามขนาดของไคลเอนต์เบราว์เซอร์จำเป็นต้องปรับขนาดและวาดใหม่ทุกครั้งที่เบราว์เซอร์ถูกปรับขนาด
วิธีแก้ปัญหาที่ไม่ซับซ้อนมากนักคือการเก็บขนาดของภาพที่สามารถวาดได้ในตัวแปรของ Javascript แต่ตั้งค่าขนาดของ canvas ตามขนาด screen.width และ screen.height ใช้ CSS เพื่อปรับให้พอดี:
#containingDiv {
overflow: hidden;
}
#myCanvas {
position: absolute;
top: 0px;
left: 0px;
}
หน้าต่างเบราว์เซอร์โดยทั่วไปจะไม่ใหญ่กว่าหน้าจอเอง (ยกเว้นในกรณีที่ความละเอียดหน้าจอรายงานผิดพลาด เช่น กับจอภาพคู่ที่ไม่ตรงกัน) ดังนั้นพื้นหลังจะไม่ปรากฏและสัดส่วนพิกเซลจะไม่เปลี่ยนแปลง พิกเซลของแคนวาสจะเป็นสัดส่วนโดยตรงกับความละเอียดหน้าจอเว้นแต่คุณจะใช้ CSS เพื่อขยายหรือย่อแคนวาส
คำตอบ #6
แนวทาง CSS บริสุทธิ์ เพิ่มเติมจากวิธีแก้ของ @jerseyboy ข้างต้น
ใช้งานได้ใน Firefox (ทดสอบในเวอร์ชัน 29), Chrome (ทดสอบในเวอร์ชัน 34) และ Internet Explorer (ทดสอบในเวอร์ชัน 11)
<!DOCTYPE html>
<html>
<head>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
canvas {
background-color: #ccc;
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.strokeRect(50,50,50,50);
}
</script>
</body>
</html>
ลิงก์ไปยังตัวอย่าง: http://temporaer.net/open/so/140502_canvas-fit-to-window.html
แต่ต้องระวังนะ ดังที่ @jerseyboy ระบุไว้ในความคิดเห็นของเขา:
การปรับขนาดผืนผ้าใบด้วย CSS เป็นเรื่องยุ่งยาก อย่างน้อยบน Chrome และ Safari ตำแหน่งเหตุการณ์เมาส์/การสัมผัสจะไม่ตรงกับตำแหน่งพิกเซลของผืนผ้าใบแบบ 1:1 และคุณจะต้องแปลงระบบพิกัด
คำตอบ #7
function resize() {
var canvas = document.getElementById('game');
var canvasRatio = canvas.height / canvas.width;
var windowRatio = window.innerHeight / window.innerWidth;
var width;
var height;
if (windowRatio < canvasRatio) {
height = window.innerHeight;
width = height / canvasRatio;
} else {
width = window.innerWidth;
height = width * canvasRatio;
}
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
};
window.addEventListener('resize', resize, false);
คำตอบ #8
ตั้งค่าขนาดเริ่มต้น
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
อัปเดตขนาดเมื่อปรับขนาดหน้าต่าง
function windowResize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.addEventListener('resize', windowResize);
