Whiteboards is a real-time application designed for people who want to collaborate on visual elements like shapes, lines, images, or drawings, for managing their work, supporting an online meeting, or drawing diagrams.
There are several challenges connected to this objective across the entire stack. Client-side performance is one of them.
How to implement real-time collaboration canvas
Before we start digging into specifics of Whiteboards, let's have a look at the landscape of possible ways of implementing a collaboration canvas:
- Native application
- Web application
- HTML5 Canvas API
- Hybrid of DOM, SVG, and HTML5 Canvas API
Whiteboards are going the last way, as it allows us to maintain rapid development pace, good maintainability, better accessibility and interactions, and what’s most important in case of performance: leveraging existing tools and optimizations of web browser engines.
Detecting performance issues
A problem can be discovered either with synthetic tests or through user feedback. While we would love to avoid the latter method, because it means dissatisfaction with the product, sometimes it’s the only way to discover unusual combinations of content and the environment.
Rendering performance can be expressed in an industry-standard metric: frame rate, which means how often the content is painted on the screen within one second. High frame rate means:
- smooth interactions, unnoticeable delay between user action, and visual feedback;
- low consumption of resources: memory, GPU, CPU, and what’s most important: the battery.
Web applications rarely function in a vacuum and need to compete for resources with other processes. For Whiteboards, it’s a common situation to fight for CPU with Zoom or Google Meet.
End users especially at work usually don’t have top-notch computers, which means not enough memory, no dedicated GPU, and not so modern CPU.
That’s why it’s important to keep our performance footprint as low as possible.
Let’s start from customer feedback:
"performance - using the whiteboard at train level (80+ users), the tool shows poor performance in terms of reactivity: moving a block can result in a real shift of the element with a delay in time of 3 to 5 seconds. Our need is to use the whiteboard managing together up to 7 agile teams planning 5 sprint each one, plus dependencies board all in the same physical whiteboard"
Our tool is designed to work fine at such a scale, so it was disappointing to learn that we failed to deliver on this promise. Keeping emotions aside, we should start from the decomposition of the problem into facts:
- 80 users → 7 teams → 5 sprints each → ~10 issues per sprint + companioning content → more than 100 visual elements on the screen → some of them moving all the time including cursors;
Our assumptions, and guiding principles:
- elements that are not visible in the viewport should not affect performance and responsiveness;
- interactions such as zooming, scrolling, modifying elements should be smooth regardless of the number of elements visible on the screen.
Creating a test environment
The problem can occur for many reasons: networking, server-side performance, application state management, style recalculations, layout, or rendering performance. In this blog post and during the analysis exercise I’m focusing on the last problem: rendering, which is the most expensive part of the page display pipeline:
My test environment consists of 350 elements, cards, lines, shapes, and drawings of various sizes layout and content:
It does not seem to be a lot of content, so let’s jump into the basic testing procedure.
Collecting performance metric
My performance smoke test procedure is straightforward: zoom in/zoom out, scroll around. That’s what a user would do as well, and my goal is to have those interactions as smooth as possible. Fortunately, I quickly notice a problem: zooming seems to be clunky. I’m not fully confident about scrolling performance either:
This is additionally embarrassing as it did not require creating enormous test data set to notice the problem, so the likelihood of noticing this issue as an end-user is very high.
Having this observation I can jump into metrics:
Looks like a problem as CPU usage goes up unexpectedly high.
It is possible to measure the frame rate in at least two ways:
- Inside the application itself using the requestAnimationFrame function, which is supposed to execute the callback once the browser is ready for the next paint;
- Using browser dev tools.
This measurement is supposed to confirm my prior observations, it will not reveal new problems.
I’m using both tools actually:
- Whiteboards mini-devtools show ~12 FPS
- Chrome frame rendering stats highlight lots of frames dropped
Both facts mean that the browser was not able to render all frames on time, so scrolling/zooming with mouse or touchpad did not result in visual feedback.
Using my prior experience, I immediately switched to this handy tool useful for analyzing what is being rendered by the browser. While hovering over various elements, I can see that they are being repainted:
This is expected - on hover, the application is adjusting the element so it is ready for interaction. There is no performance problem here.
I can see the actual problem when zooming or scrolling. Pretty much everything is repainted:
Background grid seems to be a problem
The grid is supposed to show lines accordingly to the current zoom, and viewport position, so that the content feels attached to space.
It seemed to be a perfect suspect: grid is a html5 canvas, re-rendered accordingly to zoom/scroll on each frame, however disabling it did not improve the situation.
Whiteboard content is still repainted:
Common sense: you should not re-render content if it did not change
It is reasonable to expect from browser to render Whiteboards at a low cost, because in the end, this is just a website, quite lightweight compared to sites you usually visit, that are filled with rich content, images, videos, animations, ads, trackers, social media scripts, etc.
This is the browser's responsibility to render such content in the most effective way. We don’t want to implement our own rendering engine, the Chromium team at Google has much more expertise than we will ever get. This is also not our business objective.
Whiteboards client is a React application, and from an application state management perspective - everything seemed to be fine. The only property that was changed during the interaction was CSS transform on the container element, so we expected everything to work fine - yet it did not!
It would take a whole new blog post to explain the solution. Fortunately, I can simply drop a link here:
Long story short:
- the problem was managed with will-change CSS property;
- it creates temporary visual artifacts when zooming;
- background grid must be refactored so that literally nothing will be painted during scroll/zoom.
This solution was developed a long time ago (6 months before this blog post), but it did not go live, because of the visual artifacts.
Fortunately, as of February 2021, the browser rendering engine seems to be improved, and our problems are gone, so we will be enabling it shortly.
Thanks for reading and have happy scrolling!