UNPKG

html2canvas-pro

Version:

Screenshots with JavaScript. Next generation!

116 lines 5.65 kB
"use strict"; /** * DOM Normalizer * Handles DOM side effects that need to happen before rendering * Extracted from ElementContainer to follow SRP */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DOMNormalizer = void 0; const node_type_guards_1 = require("./node-type-guards"); /** * Normalize element styles for accurate rendering * This includes disabling animations and neutralizing transforms. */ class DOMNormalizer { /** * Normalize a single element and return original styles. * * ## Why we replace transforms with an identity value instead of "none" * * `getBoundingClientRect()` returns visual (post-transform) coordinates, so we * must neutralize any active transform before measuring element bounds. * * The naive approach of setting `transform: none` (or `rotate: none`) has a * critical side-effect: per **CSS Transforms Level 2**, an element whose * `transform` is non-none automatically becomes the **containing block** for * all of its `position: absolute` *and* `position: fixed` descendants. * Setting it to `none` destroys that role, causing children to resolve their * percentage dimensions and offsets against an unintended ancestor — which * produces completely wrong bounds. * * Solution: instead of removing the transform, we replace it with a visually * inert identity value: * * - `transform: scale(0.5)` → `transform: translate(0, 0)` * - `translate(0, 0)` is an identity transform (no visual change, no layout shift). * - `getBoundingClientRect()` returns the same layout-space coordinates as * if there were no transform at all. * - Because the value is still non-none, the element **remains a containing * block** for both `position: absolute` and `position: fixed` descendants. * * - `rotate: 45deg` → `rotate: 0deg` * - `0deg` is the identity rotation; `0deg ≠ none`, so the same containing- * block guarantee holds. * * @param element - Element to normalize * @param styles - Parsed CSS styles * @returns Original styles map for restoration */ static normalizeElement(element, styles) { const originalStyles = {}; if (!(0, node_type_guards_1.isHTMLElementNode)(element)) { return originalStyles; } // Disable animations to capture static state if (styles.animationDuration.some((duration) => duration > 0)) { originalStyles.animationDuration = element.style.animationDuration; element.style.animationDuration = '0s'; } // Replace the actual transform with an identity translate so that: // 1. getBoundingClientRect() returns layout-space (unscaled/unrotated) coords. // 2. The element still satisfies "transform != none" and therefore keeps // its role as a containing block for position:absolute / position:fixed // descendants (CSS Transforms Level 2 §2.3). if (styles.transform !== null) { originalStyles.transform = element.style.transform; element.style.transform = 'translate(0, 0)'; } // Same rationale for the standalone `rotate` property. // `rotate: 0deg` is an identity rotation with no visual effect. // // However, individual transform properties (`rotate`, `translate`, `scale`) // are part of CSS Transforms Level 2 and their containing-block guarantee // is not uniformly implemented across all browsers. To be safe, if `rotate` // is the only transform-like property active on this element, we also set // `transform: translate(0, 0)` so that the containing-block role is reliably // preserved via the well-supported `transform` property. if (styles.rotate !== null) { originalStyles.rotate = element.style.rotate; element.style.rotate = '0deg'; // Individual transform properties (`rotate`, `translate`, `scale`) are // CSS Transforms Level 2 and their containing-block guarantee is not // uniformly implemented in all browsers. If `transform` was not already // set to translate(0,0) in the block above (i.e. this element has // `rotate` but no `transform`), we set it now so the containing-block // role is reliably established via the widely-supported `transform` // property – independently of browser support for individual props. if (originalStyles.transform === undefined) { originalStyles.transform = element.style.transform; element.style.transform = 'translate(0, 0)'; } } return originalStyles; } /** * Restore element styles after rendering. * * @param element - Element to restore * @param originalStyles - Original styles to restore */ static restoreElement(element, originalStyles) { if (!(0, node_type_guards_1.isHTMLElementNode)(element)) { return; } if (originalStyles.animationDuration !== undefined) { element.style.animationDuration = originalStyles.animationDuration; } if (originalStyles.transform !== undefined) { element.style.transform = originalStyles.transform; } if (originalStyles.rotate !== undefined) { element.style.rotate = originalStyles.rotate; } } } exports.DOMNormalizer = DOMNormalizer; //# sourceMappingURL=dom-normalizer.js.map