chartjs / chartjs/Chart.js

Forced re-layout (reflow) when canvas has no fixed width/height

Open
#11,826 0 comments 1 reaction 0 assignees View on GitHub
type: bug
Dominant language
JavaScript
Stars
67.7k
Forks
11.9k
Avg merge
7h 39m
Merged PRs (30d)
5

Description

### Expected behavior

Chart.js should trigger at most one forced reflow (ideally zero) upon initialization.

### Current behavior

When the `` has no `width` and `height` attributes, Chart.js currently causes multiple [forced reflows](https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing) upon initialization, probably due to interspersed reading and writing of DOM and CSS properties. This causes major slowdowns when there are many charts on a page.

Here's a devtools screenshot, showing the reflows as purple "Layout" bars:

devtools screenshot

### Reproducible sample

https://codepen.io/joliss/pen/XWwvrJL

### Optional extra steps/info to reproduce

1. Open the CodePen above
1. In Chrome devtools's Performance tab, press the ⟳ reload icon
1. Open the Frame section
1. In the flame graph, zoom in on the `main` function call to see the re-layouts

### Possible solution

- Chart.js could perhaps batch all DOM/CSS reads and writes, so that we first do all the reads, then all the writes (see the section [Avoid layout thrashing](https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing?utm_source=devtools#avoid_layout_thrashing)). This should bring the number of reflows down to one.

- For use cases where a single page has many charts, this will still slow down page load, because every chart causes a reflow, for O(n) reflows in total.

To accommodate this use case, I'd suggest it might be worthwhile for Chart.js provide a mechanism to run the "read" phase (where we determine the size using `getComputedStyle`) separately from the "write" phase (where we set DOM/CSS attributes and draw the chart). That way, as a developer, I can batch-run the read phase on all my charts, and then batch-run the write phase on all of them, bringing the number of reflows back down to one.

This mechanism doesn't need to be super obvious to access. I think as long as it's documented on the [Performance](https://www.chartjs.org/docs/master/general/performance.html) documentation page in a section like "Avoiding reflows when you have many charts", most people who need it will find it. The Performance page was the thing I checked first when I tried to solve this.

My current workaround is passing [`canvas.transferControlToOffscreen()`](https://www.chartjs.org/docs/master/general/performance.html#parallel-rendering-with-web-workers) instead of `canvas` to Chart.js, which causes all DOM/CSS writes to be ignored. This does mean that I have to do the [sizing logic](https://github.com/chartjs/Chart.js/blob/0ca1b6ef85b4668f28681f1114cc5527360e2ec9/src/helpers/helpers.dom.ts#L216-L248) manually in my own code.

### Context

_No response_

### chart.js version

v4.4.3

### Browser name and version

Chrome 126.0.6478.127

### Link to your project

_No response_

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.