fix: overflows with absolutely positioned content (#2663)

This commit is contained in:
Niklas von Hertzen 2021-08-15 19:41:57 +08:00 committed by GitHub
parent 01ed87907a
commit 38c682955a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 48 deletions

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

@ -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;
} }
} }

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

@ -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>