Responsive Canvas Stage Demo
Do you need responsive/adaptive canvas for your desktop and mobile applications?
There are many ways to make your canvas stage "responsive", and you may need different behavior for different applications.
This demo shows the simplest solution: fitting a canvas stage into the user's window with scaling. In this example, we'll focus on adjusting the stage WIDTH. You can add extra logic if you need to fit height too.
Instructions: Try resizing your browser window and see how the canvas adapts.
- Vanilla
- React
- Vue
import Konva from 'konva';
// Define virtual size for our scene
// The real size will be different to fit user's page
const sceneWidth = 1000;
const sceneHeight = 1000;
// Create stage with initial size
const stage = new Konva.Stage({
container: 'container',
width: sceneWidth,
height: sceneHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// Add circle in the center
const circle = new Konva.Circle({
radius: 50,
fill: 'red',
x: stage.width() / 2,
y: stage.height() / 2,
});
layer.add(circle);
// Add rectangle in bottom right of the stage
const rect = new Konva.Rect({
fill: 'green',
x: stage.width() - 100,
y: stage.height() - 100,
width: 100,
height: 100,
});
layer.add(rect);
// Add some text
const text = new Konva.Text({
x: 20,
y: 20,
text: 'Try resizing your browser window',
fontSize: 20,
fontFamily: 'Arial',
fill: 'black',
});
layer.add(text);
// Function to make the stage responsive
function fitStageIntoParentContainer() {
// Get the container element
const container = document.getElementById('container');
// Make the container take up the full width
container.style.width = '100%';
// Get current container width
const containerWidth = container.offsetWidth;
// Calculate scale based on virtual width vs actual width
const scale = containerWidth / sceneWidth;
// Set stage dimensions and scale
stage.width(sceneWidth * scale);
stage.height(sceneHeight * scale);
stage.scale({ x: scale, y: scale });
}
// Initial fit
fitStageIntoParentContainer();
// Adapt the stage on window resize
window.addEventListener('resize', fitStageIntoParentContainer);
import { useState, useEffect, useRef } from 'react';
import { Stage, Layer, Circle, Rect, Text } from 'react-konva';
const App = () => {
// Define virtual size for our scene
const sceneWidth = 1000;
const sceneHeight = 1000;
// State to track current scale and dimensions
const [stageSize, setStageSize] = useState({
width: sceneWidth,
height: sceneHeight,
scale: 1
});
// Reference to parent container
const containerRef = useRef(null);
// Function to handle resize
const updateSize = () => {
if (!containerRef.current) return;
// Get container width
const containerWidth = containerRef.current.offsetWidth;
// Calculate scale
const scale = containerWidth / sceneWidth;
// Update state with new dimensions
setStageSize({
width: sceneWidth * scale,
height: sceneHeight * scale,
scale: scale
});
};
// Update on mount and when window resizes
useEffect(() => {
updateSize();
window.addEventListener('resize', updateSize);
return () => {
window.removeEventListener('resize', updateSize);
};
}, []);
return (
<div
ref={containerRef}
style={{ width: '100%', height: '100%' }}
>
<Stage
width={stageSize.width}
height={stageSize.height}
scaleX={stageSize.scale}
scaleY={stageSize.scale}
>
<Layer>
<Circle
radius={50}
fill="red"
x={sceneWidth / 2}
y={sceneHeight / 2}
/>
<Rect
fill="green"
x={sceneWidth - 100}
y={sceneHeight - 100}
width={100}
height={100}
/>
<Text
x={20}
y={20}
text="Try resizing your browser window"
fontSize={20}
fontFamily="Arial"
fill="black"
/>
</Layer>
</Stage>
</div>
);
};
export default App;
<template>
<div ref="containerRef" style="width: 100%; height: 100%;">
<v-stage
:config="{
width: stageWidth,
height: stageHeight,
scaleX: scale,
scaleY: scale
}"
>
<v-layer>
<v-circle
:config="{
radius: 50,
fill: 'red',
x: sceneWidth / 2,
y: sceneHeight / 2
}"
/>
<v-rect
:config="{
fill: 'green',
x: sceneWidth - 100,
y: sceneHeight - 100,
width: 100,
height: 100
}"
/>
<v-text
:config="{
x: 20,
y: 20,
text: 'Try resizing your browser window',
fontSize: 20,
fontFamily: 'Arial',
fill: 'black'
}"
/>
</v-layer>
</v-stage>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
// Define virtual size for our scene
const sceneWidth = 1000;
const sceneHeight = 1000;
// Reactive references
const containerRef = ref(null);
const scale = ref(1);
// Computed properties for stage dimensions
const stageWidth = computed(() => sceneWidth * scale.value);
const stageHeight = computed(() => sceneHeight * scale.value);
// Function to handle resize
const updateSize = () => {
if (!containerRef.value) return;
// Get container width
const containerWidth = containerRef.value.offsetWidth;
// Calculate scale
scale.value = containerWidth / sceneWidth;
};
// Add event listeners
onMounted(() => {
updateSize();
window.addEventListener('resize', updateSize);
});
// Clean up
onUnmounted(() => {
window.removeEventListener('resize', updateSize);
});
</script>