Compare commits

...

7 Commits

15 changed files with 190 additions and 73 deletions

View File

@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.3.2](https://github.com/niklasvh/html2canvas/compare/v1.3.1...v1.3.2) (2021-08-15)
### docs
* add warning for webgl cloning with preserveDrawingBuffer=false (#2661) ([01ed879](https://github.com/niklasvh/html2canvas/commit/01ed87907ad9c7688880e2c5cb8ebc22ef73a4d8)), closes [#2661](https://github.com/niklasvh/html2canvas/issues/2661)
* include src files on www (#2660) ([58ff000](https://github.com/niklasvh/html2canvas/commit/58ff0003f77d825ac027eeec95fa80c0123eaf8f)), closes [#2660](https://github.com/niklasvh/html2canvas/issues/2660)
### feat
* add support for data-html2canvas-debug property for debugging (#2658) ([cd0d725](https://github.com/niklasvh/html2canvas/commit/cd0d7258c3a93f2989d5d9ec0244ba2763ea2d23)), closes [#2658](https://github.com/niklasvh/html2canvas/issues/2658)
### fix
* disable transition properties (#2659) ([f143166](https://github.com/niklasvh/html2canvas/commit/f1431665513e0a4636fb167a241f4a0571ba728a)), closes [#2659](https://github.com/niklasvh/html2canvas/issues/2659)
* overflows with absolutely positioned content (#2663) ([38c6829](https://github.com/niklasvh/html2canvas/commit/38c682955a9299ca7785af71d8f251df799405b0)), closes [#2663](https://github.com/niklasvh/html2canvas/issues/2663)
## [1.3.1](https://github.com/niklasvh/html2canvas/compare/v1.3.0...v1.3.1) (2021-08-14) ## [1.3.1](https://github.com/niklasvh/html2canvas/compare/v1.3.0...v1.3.1) (2021-08-14)

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "html2canvas", "name": "html2canvas",
"version": "1.3.1", "version": "1.3.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@ -6,7 +6,7 @@
"module": "dist/html2canvas.esm.js", "module": "dist/html2canvas.esm.js",
"typings": "dist/types/index.d.ts", "typings": "dist/types/index.d.ts",
"browser": "dist/html2canvas.js", "browser": "dist/html2canvas.js",
"version": "1.3.1", "version": "1.3.2",
"author": { "author": {
"name": "Niklas von Hertzen", "name": "Niklas von Hertzen",
"email": "niklasvh@gmail.com", "email": "niklasvh@gmail.com",

29
src/core/debugger.ts Normal file
View File

@ -0,0 +1,29 @@
const elementDebuggerAttribute = 'data-html2canvas-debug';
export const enum DebuggerType {
NONE,
ALL,
CLONE,
PARSE,
RENDER
}
const getElementDebugType = (element: Element): DebuggerType => {
const attribute = element.getAttribute(elementDebuggerAttribute);
switch (attribute) {
case 'all':
return DebuggerType.ALL;
case 'clone':
return DebuggerType.CLONE;
case 'parse':
return DebuggerType.PARSE;
case 'render':
return DebuggerType.RENDER;
default:
return DebuggerType.NONE;
}
};
export const isDebugging = (element: Element, type: Omit<DebuggerType, DebuggerType.NONE>): boolean => {
const elementType = getElementDebugType(element);
return elementType === DebuggerType.ALL || type === elementType;
};

View File

@ -143,7 +143,6 @@ export class CSSParsedDeclaration {
textTransform: ReturnType<typeof textTransform.parse>; textTransform: ReturnType<typeof textTransform.parse>;
transform: ReturnType<typeof transform.parse>; transform: ReturnType<typeof transform.parse>;
transformOrigin: ReturnType<typeof transformOrigin.parse>; transformOrigin: ReturnType<typeof transformOrigin.parse>;
transitionDuration: ReturnType<typeof duration.parse>;
visibility: ReturnType<typeof visibility.parse>; visibility: ReturnType<typeof visibility.parse>;
webkitTextStrokeColor: Color; webkitTextStrokeColor: Color;
webkitTextStrokeWidth: ReturnType<typeof webkitTextStrokeWidth.parse>; webkitTextStrokeWidth: ReturnType<typeof webkitTextStrokeWidth.parse>;
@ -221,7 +220,6 @@ export class CSSParsedDeclaration {
this.textTransform = parse(context, textTransform, declaration.textTransform); this.textTransform = parse(context, textTransform, declaration.textTransform);
this.transform = parse(context, transform, declaration.transform); this.transform = parse(context, transform, declaration.transform);
this.transformOrigin = parse(context, transformOrigin, declaration.transformOrigin); this.transformOrigin = parse(context, transformOrigin, declaration.transformOrigin);
this.transitionDuration = parse(context, duration, declaration.transitionDuration);
this.visibility = parse(context, visibility, declaration.visibility); this.visibility = parse(context, visibility, declaration.visibility);
this.webkitTextStrokeColor = parse(context, webkitTextStrokeColor, declaration.webkitTextStrokeColor); this.webkitTextStrokeColor = parse(context, webkitTextStrokeColor, declaration.webkitTextStrokeColor);
this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth); this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);

View File

@ -29,12 +29,24 @@ export const parseTextBounds = (
if (styles.textDecorationLine.length || text.trim().length > 0) { if (styles.textDecorationLine.length || text.trim().length > 0) {
if (FEATURES.SUPPORT_RANGE_BOUNDS) { if (FEATURES.SUPPORT_RANGE_BOUNDS) {
if (!FEATURES.SUPPORT_WORD_BREAKING) { if (!FEATURES.SUPPORT_WORD_BREAKING) {
textBounds.push( const rects = createRange(node, offset, text.length).getClientRects();
new TextBounds( if (rects.length > 1) {
text, let graphemeOffset = 0;
Bounds.fromDOMRectList(context, createRange(node, offset, text.length).getClientRects()) splitGraphemes(text).forEach((grapheme) => {
) textBounds.push(
); new TextBounds(
grapheme,
Bounds.fromDOMRectList(
context,
createRange(node, graphemeOffset + offset, grapheme.length).getClientRects()
)
)
);
graphemeOffset += grapheme.length;
});
} else {
textBounds.push(new TextBounds(text, Bounds.fromDOMRectList(context, rects)));
}
} else { } else {
textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length))); textBounds.push(new TextBounds(text, getRangeBounds(context, node, offset, text.length)));
} }

View File

@ -5,7 +5,8 @@ export enum OVERFLOW {
VISIBLE = 0, VISIBLE = 0,
HIDDEN = 1, HIDDEN = 1,
SCROLL = 2, SCROLL = 2,
AUTO = 3 CLIP = 3,
AUTO = 4
} }
export const overflow: IPropertyListDescriptor<OVERFLOW[]> = { export const overflow: IPropertyListDescriptor<OVERFLOW[]> = {
@ -20,6 +21,8 @@ export const overflow: IPropertyListDescriptor<OVERFLOW[]> = {
return OVERFLOW.HIDDEN; return OVERFLOW.HIDDEN;
case 'scroll': case 'scroll':
return OVERFLOW.SCROLL; return OVERFLOW.SCROLL;
case 'clip':
return OVERFLOW.CLIP;
case 'auto': case 'auto':
return OVERFLOW.AUTO; return OVERFLOW.AUTO;
case 'visible': case 'visible':

View File

@ -20,6 +20,7 @@ import {LIST_STYLE_TYPE, listStyleType} from '../css/property-descriptors/list-s
import {CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration} from '../css/index'; import {CSSParsedCounterDeclaration, CSSParsedPseudoDeclaration} from '../css/index';
import {getQuote} from '../css/property-descriptors/quotes'; import {getQuote} from '../css/property-descriptors/quotes';
import {Context} from '../core/context'; import {Context} from '../core/context';
import {DebuggerType, isDebugging} from '../core/debugger';
export interface CloneOptions { export interface CloneOptions {
ignoreElements?: (element: Element) => boolean; ignoreElements?: (element: Element) => boolean;
@ -136,6 +137,9 @@ export class DocumentCloner {
} }
createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement { createElementClone<T extends HTMLElement | SVGElement>(node: T): HTMLElement | SVGElement {
if (isDebugging(node, DebuggerType.CLONE)) {
debugger;
}
if (isCanvasElement(node)) { if (isCanvasElement(node)) {
return this.createCanvasClone(node); return this.createCanvasClone(node);
} }
@ -190,7 +194,7 @@ export class DocumentCloner {
img.src = canvas.toDataURL(); img.src = canvas.toDataURL();
return img; return img;
} catch (e) { } catch (e) {
this.context.logger.info(`Unable to clone canvas contents, canvas is tainted`); this.context.logger.info(`Unable to inline canvas contents, canvas is tainted`, canvas);
} }
} }
@ -205,12 +209,23 @@ export class DocumentCloner {
if (!this.options.allowTaint && ctx) { if (!this.options.allowTaint && ctx) {
clonedCtx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0); clonedCtx.putImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), 0, 0);
} else { } else {
const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');
if (gl) {
const attribs = gl.getContextAttributes();
if (attribs?.preserveDrawingBuffer === false) {
this.context.logger.warn(
'Unable to clone WebGL context as it has preserveDrawingBuffer=false',
canvas
);
}
}
clonedCtx.drawImage(canvas, 0, 0); clonedCtx.drawImage(canvas, 0, 0);
} }
} }
return clonedCanvas; return clonedCanvas;
} catch (e) { } catch (e) {
this.context.logger.info(`Unable to clone canvas as it is tainted`); this.context.logger.info(`Unable to clone canvas as it is tainted`, canvas);
} }
return clonedCanvas; return clonedCanvas;
@ -229,6 +244,7 @@ export class DocumentCloner {
if (window && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) { if (window && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) {
const clone = this.createElementClone(node); const clone = this.createElementClone(node);
clone.style.transitionProperty = 'none';
const style = window.getComputedStyle(node); const style = window.getComputedStyle(node);
const styleBefore = window.getComputedStyle(node, ':before'); const styleBefore = window.getComputedStyle(node, ':before');

View File

@ -3,32 +3,33 @@ import {TextContainer} from './text-container';
import {Bounds, parseBounds} from '../css/layout/bounds'; import {Bounds, parseBounds} from '../css/layout/bounds';
import {isHTMLElementNode} from './node-parser'; import {isHTMLElementNode} from './node-parser';
import {Context} from '../core/context'; import {Context} from '../core/context';
import {DebuggerType, isDebugging} from '../core/debugger';
export const enum FLAGS { export const enum FLAGS {
CREATES_STACKING_CONTEXT = 1 << 1, CREATES_STACKING_CONTEXT = 1 << 1,
CREATES_REAL_STACKING_CONTEXT = 1 << 2, CREATES_REAL_STACKING_CONTEXT = 1 << 2,
IS_LIST_OWNER = 1 << 3 IS_LIST_OWNER = 1 << 3,
DEBUG_RENDER = 1 << 4
} }
export class ElementContainer { export class ElementContainer {
readonly styles: CSSParsedDeclaration; readonly styles: CSSParsedDeclaration;
readonly textNodes: TextContainer[]; readonly textNodes: TextContainer[] = [];
readonly elements: ElementContainer[]; readonly elements: ElementContainer[] = [];
bounds: Bounds; bounds: Bounds;
flags: number; flags = 0;
constructor(protected readonly context: Context, element: Element) { constructor(protected readonly context: Context, element: Element) {
if (isDebugging(element, DebuggerType.PARSE)) {
debugger;
}
this.styles = new CSSParsedDeclaration(context, window.getComputedStyle(element, null)); this.styles = new CSSParsedDeclaration(context, window.getComputedStyle(element, null));
this.textNodes = [];
this.elements = [];
if (isHTMLElementNode(element)) { if (isHTMLElementNode(element)) {
if (this.styles.animationDuration.some((duration) => duration > 0)) { if (this.styles.animationDuration.some((duration) => duration > 0)) {
element.style.animationDuration = '0s'; element.style.animationDuration = '0s';
} }
if (this.styles.transitionDuration.some((duration) => duration > 0)) {
element.style.transitionDuration = '0s';
}
if (this.styles.transform !== null) { if (this.styles.transform !== null) {
// getBoundingClientRect takes transforms into account // getBoundingClientRect takes transforms into account
@ -37,6 +38,9 @@ export class ElementContainer {
} }
this.bounds = parseBounds(this.context, element); this.bounds = parseBounds(this.context, element);
this.flags = 0;
if (isDebugging(element, DebuggerType.RENDER)) {
this.flags |= FLAGS.DEBUG_RENDER;
}
} }
} }

View File

@ -1,6 +1,6 @@
import {ElementPaint, parseStackingContexts, StackingContext} from '../stacking-context'; import {ElementPaint, parseStackingContexts, StackingContext} from '../stacking-context';
import {asString, Color, isTransparent} from '../../css/types/color'; import {asString, Color, isTransparent} from '../../css/types/color';
import {ElementContainer} from '../../dom/element-container'; import {ElementContainer, FLAGS} from '../../dom/element-container';
import {BORDER_STYLE} from '../../css/property-descriptors/border-style'; import {BORDER_STYLE} from '../../css/property-descriptors/border-style';
import {CSSParsedDeclaration} from '../../css/index'; import {CSSParsedDeclaration} from '../../css/index';
import {TextContainer} from '../../dom/text-container'; import {TextContainer} from '../../dom/text-container';
@ -87,12 +87,12 @@ export class CanvasRenderer extends Renderer {
); );
} }
applyEffects(effects: IElementEffect[], target: EffectTarget): void { applyEffects(effects: IElementEffect[]): void {
while (this._activeEffects.length) { while (this._activeEffects.length) {
this.popEffect(); this.popEffect();
} }
effects.filter((effect) => contains(effect.target, target)).forEach((effect) => this.applyEffect(effect)); effects.forEach((effect) => this.applyEffect(effect));
} }
applyEffect(effect: IElementEffect): void { applyEffect(effect: IElementEffect): void {
@ -135,6 +135,10 @@ export class CanvasRenderer extends Renderer {
} }
async renderNode(paint: ElementPaint): Promise<void> { async renderNode(paint: ElementPaint): Promise<void> {
if (contains(paint.container.flags, FLAGS.DEBUG_RENDER)) {
debugger;
}
if (paint.container.styles.isVisible()) { if (paint.container.styles.isVisible()) {
await this.renderNodeBackgroundAndBorders(paint); await this.renderNodeBackgroundAndBorders(paint);
await this.renderNodeContent(paint); await this.renderNodeContent(paint);
@ -289,7 +293,7 @@ export class CanvasRenderer extends Renderer {
} }
async renderNodeContent(paint: ElementPaint): Promise<void> { async renderNodeContent(paint: ElementPaint): Promise<void> {
this.applyEffects(paint.effects, EffectTarget.CONTENT); this.applyEffects(paint.getEffects(EffectTarget.CONTENT));
const container = paint.container; const container = paint.container;
const curves = paint.curves; const curves = paint.curves;
const styles = container.styles; const styles = container.styles;
@ -468,6 +472,9 @@ export class CanvasRenderer extends Renderer {
} }
async renderStackContent(stack: StackingContext): Promise<void> { async renderStackContent(stack: StackingContext): Promise<void> {
if (contains(stack.element.container.flags, FLAGS.DEBUG_RENDER)) {
debugger;
}
// https://www.w3.org/TR/css-position-3/#painting-order // https://www.w3.org/TR/css-position-3/#painting-order
// 1. the background and borders of the element forming the stacking context. // 1. the background and borders of the element forming the stacking context.
await this.renderNodeBackgroundAndBorders(stack.element); await this.renderNodeBackgroundAndBorders(stack.element);
@ -685,7 +692,7 @@ export class CanvasRenderer extends Renderer {
} }
async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise<void> { async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise<void> {
this.applyEffects(paint.effects, EffectTarget.BACKGROUND_BORDERS); this.applyEffects(paint.getEffects(EffectTarget.BACKGROUND_BORDERS));
const styles = paint.container.styles; const styles = paint.container.styles;
const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length; const hasBackground = !isTransparent(styles.backgroundColor) || styles.backgroundImage.length;
@ -899,7 +906,7 @@ export class CanvasRenderer extends Renderer {
const stack = parseStackingContexts(element); const stack = parseStackingContexts(element);
await this.renderStack(stack); await this.renderStack(stack);
this.applyEffects([], EffectTarget.BACKGROUND_BORDERS); this.applyEffects([]);
return this.canvas; return this.canvas;
} }
} }

View File

@ -20,36 +20,21 @@ export interface IElementEffect {
export class TransformEffect implements IElementEffect { export class TransformEffect implements IElementEffect {
readonly type: EffectType = EffectType.TRANSFORM; readonly type: EffectType = EffectType.TRANSFORM;
readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT;
readonly offsetX: number;
readonly offsetY: number;
readonly matrix: Matrix;
constructor(offsetX: number, offsetY: number, matrix: Matrix) { constructor(readonly offsetX: number, readonly offsetY: number, readonly matrix: Matrix) {}
this.offsetX = offsetX;
this.offsetY = offsetY;
this.matrix = matrix;
}
} }
export class ClipEffect implements IElementEffect { export class ClipEffect implements IElementEffect {
readonly type: EffectType = EffectType.CLIP; readonly type: EffectType = EffectType.CLIP;
readonly target: number;
readonly path: Path[];
constructor(path: Path[], target: EffectTarget) { constructor(readonly path: Path[], readonly target: EffectTarget) {}
this.target = target;
this.path = path;
}
} }
export class OpacityEffect implements IElementEffect { export class OpacityEffect implements IElementEffect {
readonly type: EffectType = EffectType.OPACITY; readonly type: EffectType = EffectType.OPACITY;
readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT; readonly target: number = EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT;
readonly opacity: number;
constructor(opacity: number) { constructor(readonly opacity: number) {}
this.opacity = opacity;
}
} }
export const isTransformEffect = (effect: IElementEffect): effect is TransformEffect => export const isTransformEffect = (effect: IElementEffect): effect is TransformEffect =>

View File

@ -1,13 +1,14 @@
import {ElementContainer, FLAGS} from '../dom/element-container'; import {ElementContainer, FLAGS} from '../dom/element-container';
import {contains} from '../core/bitwise'; import {contains} from '../core/bitwise';
import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves'; import {BoundCurves, calculateBorderBoxPath, calculatePaddingBoxPath} from './bound-curves';
import {ClipEffect, EffectTarget, IElementEffect, OpacityEffect, TransformEffect} from './effects'; import {ClipEffect, EffectTarget, IElementEffect, isClipEffect, OpacityEffect, TransformEffect} from './effects';
import {OVERFLOW} from '../css/property-descriptors/overflow'; import {OVERFLOW} from '../css/property-descriptors/overflow';
import {equalPath} from './path'; import {equalPath} from './path';
import {DISPLAY} from '../css/property-descriptors/display'; import {DISPLAY} from '../css/property-descriptors/display';
import {OLElementContainer} from '../dom/elements/ol-element-container'; import {OLElementContainer} from '../dom/elements/ol-element-container';
import {LIElementContainer} from '../dom/elements/li-element-container'; import {LIElementContainer} from '../dom/elements/li-element-container';
import {createCounterText} from '../css/types/functions/counter'; import {createCounterText} from '../css/types/functions/counter';
import {POSITION} from '../css/property-descriptors/position';
export class StackingContext { export class StackingContext {
element: ElementPaint; element: ElementPaint;
@ -32,27 +33,24 @@ export class StackingContext {
} }
export class ElementPaint { export class ElementPaint {
container: ElementContainer; readonly effects: IElementEffect[] = [];
effects: IElementEffect[]; readonly curves: BoundCurves;
curves: BoundCurves;
listValue?: string; listValue?: string;
constructor(element: ElementContainer, parentStack: IElementEffect[]) { constructor(readonly container: ElementContainer, readonly parent: ElementPaint | null) {
this.container = element; this.curves = new BoundCurves(this.container);
this.effects = parentStack.slice(0); if (this.container.styles.opacity < 1) {
this.curves = new BoundCurves(element); this.effects.push(new OpacityEffect(this.container.styles.opacity));
if (element.styles.opacity < 1) {
this.effects.push(new OpacityEffect(element.styles.opacity));
} }
if (element.styles.transform !== null) { if (this.container.styles.transform !== null) {
const offsetX = element.bounds.left + element.styles.transformOrigin[0].number; const offsetX = this.container.bounds.left + this.container.styles.transformOrigin[0].number;
const offsetY = element.bounds.top + element.styles.transformOrigin[1].number; const offsetY = this.container.bounds.top + this.container.styles.transformOrigin[1].number;
const matrix = element.styles.transform; const matrix = this.container.styles.transform;
this.effects.push(new TransformEffect(offsetX, offsetY, matrix)); this.effects.push(new TransformEffect(offsetX, offsetY, matrix));
} }
if (element.styles.overflowX !== OVERFLOW.VISIBLE) { if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) {
const borderBox = calculateBorderBoxPath(this.curves); const borderBox = calculateBorderBoxPath(this.curves);
const paddingBox = calculatePaddingBoxPath(this.curves); const paddingBox = calculatePaddingBoxPath(this.curves);
@ -65,16 +63,32 @@ export class ElementPaint {
} }
} }
getParentEffects(): IElementEffect[] { getEffects(target: EffectTarget): IElementEffect[] {
let inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) === -1;
let parent = this.parent;
const effects = this.effects.slice(0); const effects = this.effects.slice(0);
if (this.container.styles.overflowX !== OVERFLOW.VISIBLE) { while (parent) {
const borderBox = calculateBorderBoxPath(this.curves); const croplessEffects = parent.effects.filter((effect) => !isClipEffect(effect));
const paddingBox = calculatePaddingBoxPath(this.curves); if (inFlow || parent.container.styles.position !== POSITION.STATIC || !parent.parent) {
if (!equalPath(borderBox, paddingBox)) { effects.unshift(...croplessEffects);
effects.push(new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)); inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(parent.container.styles.position) === -1;
if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) {
const borderBox = calculateBorderBoxPath(parent.curves);
const paddingBox = calculatePaddingBoxPath(parent.curves);
if (!equalPath(borderBox, paddingBox)) {
effects.unshift(
new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)
);
}
}
} else {
effects.unshift(...croplessEffects);
} }
parent = parent.parent;
} }
return effects;
return effects.filter((effect) => contains(effect.target, target));
} }
} }
@ -87,7 +101,7 @@ const parseStackTree = (
parent.container.elements.forEach((child) => { parent.container.elements.forEach((child) => {
const treatAsRealStackingContext = contains(child.flags, FLAGS.CREATES_REAL_STACKING_CONTEXT); const treatAsRealStackingContext = contains(child.flags, FLAGS.CREATES_REAL_STACKING_CONTEXT);
const createsStackingContext = contains(child.flags, FLAGS.CREATES_STACKING_CONTEXT); const createsStackingContext = contains(child.flags, FLAGS.CREATES_STACKING_CONTEXT);
const paintContainer = new ElementPaint(child, parent.getParentEffects()); const paintContainer = new ElementPaint(child, parent);
if (contains(child.styles.display, DISPLAY.LIST_ITEM)) { if (contains(child.styles.display, DISPLAY.LIST_ITEM)) {
listItems.push(paintContainer); listItems.push(paintContainer);
} }
@ -182,7 +196,7 @@ const processListItems = (owner: ElementContainer, elements: ElementPaint[]) =>
}; };
export const parseStackingContexts = (container: ElementContainer): StackingContext => { export const parseStackingContexts = (container: ElementContainer): StackingContext => {
const paintContainer = new ElementPaint(container, []); const paintContainer = new ElementPaint(container, null);
const root = new StackingContext(paintContainer); const root = new StackingContext(paintContainer);
const listItems: ElementPaint[] = []; const listItems: ElementPaint[] = [];
parseStackTree(paintContainer, root, root, listItems); parseStackTree(paintContainer, root, root, listItems);

View File

@ -55,6 +55,11 @@
transform: rotate(45deg) transform: rotate(45deg)
} }
.transition-delay {
transition: 1ms;
transition-delay: 50ms;
transform: rotate(45deg)
}
div { div {
float: left; float: left;
@ -84,5 +89,9 @@
<div class="transitioned broken"> <div class="transitioned broken">
<p>Hello</p> <p>Hello</p>
</div> </div>
<div class="transition-delay">
<p>Hello</p>
</div>
</body> </body>
</html> </html>

View File

@ -51,6 +51,9 @@
.scroll { .scroll {
overflow: scroll; overflow: scroll;
} }
.clip {
overflow: clip;
}
.auto { .auto {
overflow: auto; overflow: auto;
} }
@ -70,6 +73,10 @@
scroll scroll
<p class="scroll">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p> <p class="scroll">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div> </div>
<div class="cell">
clip
<p class="clip">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
</div>
<div class="cell"> <div class="cell">
auto auto
<p class="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p> <p class="auto">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec luctus pretium facilisis. Praesent rutrum eget nisl in tristique. Sed tincidunt nisl et tellus vulputate, nec rhoncus orci pretium.</p>
@ -129,5 +136,17 @@
</script> </script>
</div> </div>
<div class="hidden">Hidden<div style="opacity: 0.5">With opacity</div></div> <div class="hidden">Hidden<div style="opacity: 0.5">With opacity</div></div>
<div class="hidden" style="height: 0px; width: 50px;">
<div style="position: absolute; width: 50px; height: 50px; left:400px;">absolute on static parent</div>
</div>
<div class="hidden" style="height: 0px; width: 50px;">
<div style="position: relative; width: 10px; height: 10px; left:400px;">relative on static parent</div>
</div>
<div class="hidden" style="height: 0px; width: 50px;">
<div style="position: fixed; width: 50px; height: 50px; left:400px; top:0;">fixed on static parent</div>
</div>
<div class="hidden" style="height: 0px; width: 50px;position: relative;">
<div style="position: absolute; width: 10px; height: 10px; left:400px;">absolute on relative parent</div>
</div>
</body> </body>
</html> </html>

View File

@ -32,8 +32,10 @@
"license": "MIT", "license": "MIT",
"main": "n/a", "main": "n/a",
"scripts": { "scripts": {
"copybuild": "mkdirp public/dist && cpy ../dist/*.js public/dist", "copy:build": "mkdirp public/dist && cpy ../dist/*.js public/dist",
"build": "npm run copybuild && gatsby build", "copy:src": "mkdirp public/src && cpy ../src/**/*.ts public/src --parents",
"copy": "npm run copy:build && npm run copy:src",
"build": "npm run copy && gatsby build",
"start": "gatsby develop", "start": "gatsby develop",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
} }