HTML5 Large Canvas Scrolling Demo

Imagine we have this scenario. There are a very large stage 3000x3000 with many nodes inside.
User wants to take a look into all nodes, but they are not visible at once.

How to display and scroll a very big html5 canvas?

Lets think you have a very large canvas and you want to add ability to navigate on it.

I will show your 4 different approaches to achieve that:

1. Just make large stage

This is the simplest approach. But it is very slow, because large canvases are slow.
User will be able to scroll with native scrollbars.

Pros:

  • Simple implementation

Cons:

  • Slow
Show source code!


Canvas Scrolling Largeview raw
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/konva@2.4.2/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Canvas Scrolling Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
height: 100%;
overflow: auto;
}
</style>
</head>

<body>
<div id="container"></div>
<script>

var WIDTH = 3000;
var HEIGHT = 3000;
var NUMBER = 200;

var stage = new Konva.Stage({
container: 'container',
width: WIDTH,
height: HEIGHT,
});

var layer = new Konva.Layer();
stage.add(layer);




function generateNode() {
return new Konva.Circle({
x: WIDTH * Math.random(),
y: HEIGHT * Math.random(),
radius: 50,
fill: 'red',
stroke: 'black'
});
}

for (var i = 0; i < NUMBER; i++) {
layer.add(generateNode());
}
layer.draw();
</script>

</body>

</html>


2. Make stage draggable (navigate with drag&drop)

That one is better because stage is much smaller.

Pros:

  • Simple implementation
  • Fast

Cons

  • Sometimes drag&drop navigation is not the best UX
Show source code!


Canvas Scrolling Dragview raw
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/konva@2.4.2/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Canvas Scrolling Drag Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>

<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
draggable: true
});

var layer = new Konva.Layer();
stage.add(layer);

var WIDTH = 3000;
var HEIGHT = 3000;
var NUMBER = 200;


function generateNode() {
return new Konva.Circle({
x: WIDTH * Math.random(),
y: HEIGHT * Math.random(),
radius: 50,
fill: 'red',
stroke: 'black'
});
}

for (var i = 0; i < NUMBER; i++) {
layer.add(generateNode());
}
layer.draw();
</script>

</body>

</html>


3. Emulate scrollbars.

You will have to draw them manually and implement all moving functionality.
That is quite a lot of work. But works good for many apps.

Instruction: try to scroll with bars.

Pros:

  • Works ok
  • Intuitive scroll
  • Fast

Cons

  • Scrollbars are not native, so you have to implement many things manually (like scroll with keyboard)
Show source code!


Canvas Scrolling Barsview raw
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/konva@2.4.2/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Canvas Scrolling Drag Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>

<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});

var layer = new Konva.Layer();
stage.add(layer);

var WIDTH = 3000;
var HEIGHT = 3000;
var NUMBER = 200;


function generateNode() {
return new Konva.Circle({
x: WIDTH * Math.random(),
y: HEIGHT * Math.random(),
radius: 50,
fill: 'red',
stroke: 'black'
});
}

for (var i = 0; i < NUMBER; i++) {
layer.add(generateNode());
}
layer.draw();

// now draw our bars
var scrollLayers = new Konva.Layer();
stage.add(scrollLayers);

const PADDING = 5;

var verticalBar = new Konva.Rect({
width: 10,
height: 100,
fill: 'grey',
opacity: 0.8,
x: stage.width() - PADDING - 10,
y: PADDING,
draggable: true,
dragBoundFunc: function (pos) {
pos.x = stage.width() - PADDING - 10;
pos.y = Math.max(Math.min(pos.y, stage.height() - this.height() - PADDING), PADDING);
return pos;
}
});
scrollLayers.add(verticalBar);
scrollLayers.draw();

verticalBar.on('dragmove', function () {
// delta in %
const availableHeight = stage.height() - PADDING * 2 - verticalBar.height();
var delta = (verticalBar.y() - PADDING) / availableHeight;

layer.y(-stage.height() * delta);
layer.batchDraw();
});


var horizontalBar = new Konva.Rect({
width: 100,
height: 10,
fill: 'grey',
opacity: 0.8,
x: PADDING,
y: stage.height() - PADDING - 10,
draggable: true,
dragBoundFunc: function (pos) {
pos.x = Math.max(Math.min(pos.x, stage.width() - this.width() - PADDING), PADDING);
pos.y = stage.height() - PADDING - 10;

return pos;
}
});
scrollLayers.add(horizontalBar);
scrollLayers.draw();

horizontalBar.on('dragmove', function () {
// delta in %
const availableWidth = stage.width() - PADDING * 2 - horizontalBar.width();
var delta = (horizontalBar.x() - PADDING) / availableWidth;

layer.x(-stage.width() * delta);
layer.batchDraw();
});

</script>

</body>

</html>


4. Emulate screen moving with transform.

That demo works really good, but it may be tricky.
The idea is:

  • We will use small canvas with the size of the screen
  • We will create container with required size (3000x3000), so native scrollbars will be visible
  • When user is trying to scroll, we will apply css transform for the stage container so it will be still in the center of user’s screen
  • We will move all nodes so it looks like you scroll (by changing stage position)

Props:

  • Works perfect and fast
  • Native scrolling

Cons:

  • You have to understand what is going on.

Instruction: try to scroll with native bars.

Show source code!


Canvas Scrolling Transformview raw
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/konva@2.4.2/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Canvas Scrolling Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
height: 100%;
overflow: auto;
}

#large-container {
width: 3000px;
height: 3000px;
overflow: hidden;
}

#scroll-container {
width: calc(100% - 22px);
height: calc(100vh - 22px);
overflow: auto;
margin: 10px;
border: 1px solid grey;
}
</style>
</head>

<body>
<div id="scroll-container">
<div id="large-container">
<div id="container"></div>
</div>
</div>
<script>

var WIDTH = 3000;
var HEIGHT = 3000;
var NUMBER = 200;

var stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});

var layer = new Konva.Layer();
stage.add(layer);

function generateNode() {
return new Konva.Circle({
x: WIDTH * Math.random(),
y: HEIGHT * Math.random(),
radius: 50,
fill: 'red',
stroke: 'black'
});
}

for (var i = 0; i < NUMBER; i++) {
layer.add(generateNode());
}
layer.draw();

var scrollContainer = document.getElementById('scroll-container');
scrollContainer.addEventListener('scroll', function () {
var dx = scrollContainer.scrollLeft;
var dy = scrollContainer.scrollTop;
stage.container().style.transform = 'translate(' + dx + 'px, ' + dy + 'px)';
stage.x(-dx);
stage.y(-dy);
stage.batchDraw();
})
</script>

</body>

</html>


Next