mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
fix: overflows with absolutely positioned content (#2663)
This commit is contained in:
parent
01ed87907a
commit
38c682955a
@ -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':
|
||||||
|
@ -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 {
|
||||||
@ -293,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;
|
||||||
@ -692,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;
|
||||||
|
|
||||||
@ -906,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 =>
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user