Adding zoom and panning to your react-konva stage
— JavaScript, Software Development — 3 min read
In my last blog I talked about some of the work I’ve been doing with react-konva in order to produce an example user journey map for the new product I’m working on — reciprocal.dev .
In that post I created a very basic user journey which had one logical branch, which, while great for showing the techniques being used isn’t really reflective of the scale of most user journey maps once they have a high level of functionality.
As the width of the Stage
was set to window.innerWidth
in the example, there is also the constraint of the user’s browser window. Anyone viewing on a small screen or with a small window size would have the map cut off.
Rendering content larger than the stage size
Without adding some additional functionality to your Konva based view there will be no means of accessing anything plotted outside the bounds of the initial stage coordinates.
In order to access this content you can use and combine two techniques that will allow the user to change the stage’s viewbox coordinates (panning) and the scale of the content rendered (zooming).
Panning
Panning is probably the easiest solution to implement, it requires you to add one prop to your Stage
component which is draggable
.
With the draggable
prop the user will now be able to click on your stage and drag it in order to change the current viewbox and by doing so, change what content is visible.
If you already have draggable content on your Stage
then you won’t need to worry about those breaking as the Stage
drag events only fire when the user selects the ‘background’ of the stage.
Zooming
Zooming is a little more complicated as we need to implement some custom logic to update the scale that the objects in the Stage
are rendered to.
The scale of a Stage
is comprised of two values — the scaleX
and scaleY
which can have different values but for uniform scaling they should be set to the same value.
By default the scale of the Stage
is 1
. Setting the scale to a value higher than 1
will make the items larger and setting the scale to a value betweem 0
and 1
will make them smaller.
In order to implement a zoom behaviour to our stage we need to add event listeners for the following events:
onWheel
onTouchMove
OnTouchEnd
The first event OnWheel
will listen for a user scrolling up and down while the last two are used for touch screen devices in order to detect the user using two fingers to pinch and pull the stage.
When the user does these events we want to:
- Understand what the current position is relative to the current scale
- Calculate the new scale based on a zoom factor
- Calculate that same position based on the new scale
- Update the
Stage
to render using the new scale - Update the
Stage
‘s position to be the same as before but with the new value based on the new scale
For the touch screen based approach we use onTouchEnd
to reset the values used to calculate the zoom. Another important step for touch based zooming is that the draggable
prop needs to be set to force otherwise Konva gets confused and doesn’t listen to the events properly.
An implementation of this behaviour can be found below.
Keeping things performant
If you have a complex or large map to render on your stage you may find that performance takes a nosedive as soon as you try to pan the stage.
The image shows the drop in the frames per second rendering in Chrome when I performed a pan on my initial implementation.
Luckily there is an easy fix for this — disabling perfect drawing. By default all objects in Konva are set to be drawn to a high accuracy, which is fine for static rendering but once you start moving things about the computational needs of doing this results in frames taking longer than 16ms to render.
To disable perfect drawing you just need to add a perfectDrawEnabled={false}
prop to all your react-konva components, after doing so I had a far more responsive panning experience.
Summary
Using panning and zooming allows you to render content that is larger than the initial Stage
while giving the user tools to interact with the content in a manner that feels familiar.
Konva by default will enable perfect drawing which works well for static content but this will kill the performance of the app when the user interacts with it so turn this off to keep those interactions snappy.