| name | performance-dom |
| description | Browser rendering performance for Trezor Suite — forced synchronous layout from geometry reads, observer APIs instead of measuring an element, and transitioning only compositor properties. Use when measuring, scrolling or animating a DOM element, or when writing a CSS transition. Web and desktop only. |
DOM Manipulation and CSS Properties
Reads that force the browser to lay out again mid-frame, and animations that make it lay out on every
frame. Web and desktop only.
Optimize DOM reads and writes, and never interleave them
All of the below properties or methods, when requested/called in JavaScript, will trigger the browser to synchronously calculate the style and layout*. This is also called reflow or layout thrashing, and is common performance bottleneck.
Generally, all APIs that synchronously provide layout metrics will trigger forced reflow / layout. Read on for additional cases and details
Getting box metrics
elem.offsetLeft, elem.offsetTop, elem.offsetWidth, elem.offsetHeight, elem.offsetParent
elem.clientLeft, elem.clientTop, elem.clientWidth, elem.clientHeight
elem.getClientRects(), elem.getBoundingClientRect()
Scroll stuff
-
elem.scrollBy(), elem.scrollTo()
-
elem.scrollIntoView(), elem.scrollIntoViewIfNeeded()
-
elem.scrollWidth, elem.scrollHeight
-
elem.scrollLeft, elem.scrollTop also, setting them
-
elem.focus() (source)
-
elem.computedRole, elem.computedName
-
elem.innerText (source)
-
window.scrollX, window.scrollY
-
window.innerHeight, window.innerWidth
-
window.visualViewport.height / width / offsetTop / offsetLeft (source)
-
document.scrollingElement only forces style
-
document.elementFromPoint
-
inputElem.focus()
-
inputElem.select(), textareaElem.select()
-
mouseEvt.layerX, mouseEvt.layerY, mouseEvt.offsetX, mouseEvt.offsetY (source)
window.getComputedStyle() will typically force style recalc.
window.getComputedStyle() will often force layout, as well.
Details of the conditions where gCS() forces layout
window.getComputedStyle() will force layout in one of 3 conditions:
- The element is in a shadow tree
- There are media queries (viewport-related ones). Specifically, one of the following: (source
min-width, min-height, max-width, max-height, width, height
aspect-ratio, min-aspect-ratio, max-aspect-ratio
device-pixel-ratio, resolution, orientation , min-device-pixel-ratio, max-device-pixel-ratio
- The property requested is one of the following: (source)
height, width
top, right, bottom, left
margin [-top, -right, -bottom, -left, or shorthand] only if the margin is fixed.
padding [-top, -right, -bottom, -left, or shorthand] only if the padding is fixed.
transform, transform-origin, perspective-origin
translate, rotate, scale
grid, grid-template, grid-template-columns, grid-template-rows
perspective-origin
- These items were previously in the list but appear to not be any longer (as of Feb 2018):
motion-path, motion-offset, motion-rotation, x, y, rx,
range.getClientRects(), range.getBoundingClientRect()
SVG
Quite a lot of properties/methods force. This list in incomplete:
-
SVGLocatable: computeCTM(), getBBox()
-
SVGTextContent: getCharNumAtPosition(), getComputedTextLength(), getEndPositionOfChar(), getExtentOfChar(), getNumberOfChars(), getRotationOfChar(), getStartPositionOfChar(), getSubStringLength(), selectSubString()
-
SVGUse: instanceRoot
-
Lots & lots of stuff, including copying an image to clipboard (source)
Source
inner.style.width = `${inner.clientWidth}px`;
container.style.width = `${inner.clientWidth}px`;
const innerWidth = inner.clientWidth;
inner.style.width = `${innerWidth}px`;
container.style.width = `${innerWidth}px`;
- Where geometry can come from an observer, take it there: an
IntersectionObserver or ResizeObserver
callback runs after layout, so reading in it is cheap (example: useAnchor.ts:25).
requestAnimationFrame requests the browser to call a user-supplied callback function before the next repaint.
Transition compositor properties, and never leave the property unnamed
width, height, top, margin and min-width re-run layout on every frame of the animation
transform, opacity are composited and do not.
transition: all is the same defect with nothing named — and so is the bare-duration shorthand, because an omitted transition-property resets to all.
width: ${({ $max, $value }) => `calc((100% / ${$max}) * ${$value})`};
transition: width 0.5s;
width: 100%;
transform-origin: left;
transform: scaleX(${({ $max, $value }) => $value / $max});
transition: transform 0.5s;
transform-origin is load-bearing: the default origin is the centre, so without it every bar in the app
grows from its middle. Scale is also the weakest case for transform — a scale change re-rasters unless
will-change: transform pins the texture, and then it stretches a bitmap — so it is safe for a solid
childless fill like this one and wrong for a pill-shaped or labelled bar, where it distorts the radius and
the text. Translation and opacity are the genuinely composite-only changes.
Related skills