fix: make isIntersectingViewport work with SVG elements (#10004)
This commit is contained in:
parent
ab27f738c9
commit
656b562c74
@ -4,7 +4,7 @@ sidebar_label: ElementHandle.isIntersectingViewport
|
||||
|
||||
# ElementHandle.isIntersectingViewport() method
|
||||
|
||||
Resolves to true if the element is visible in the current viewport.
|
||||
Resolves to true if the element is visible in the current viewport. If an element is an SVG, we check if the svg owner element is in the viewport instead. See https://crbug.com/963246.
|
||||
|
||||
#### Signature:
|
||||
|
||||
|
@ -67,7 +67,7 @@ The constructor for this class is marked as internal. Third-party code should no
|
||||
| [drop(this, data)](./puppeteer.elementhandle.drop.md) | | This method triggers a drop on the element. |
|
||||
| [focus()](./puppeteer.elementhandle.focus.md) | | Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element. |
|
||||
| [hover(this)](./puppeteer.elementhandle.hover.md) | | This method scrolls element into view if needed, and then uses [Page](./puppeteer.page.md) to hover over the center of the element. If the element is detached from DOM, the method throws an error. |
|
||||
| [isIntersectingViewport(this, options)](./puppeteer.elementhandle.isintersectingviewport.md) | | Resolves to true if the element is visible in the current viewport. |
|
||||
| [isIntersectingViewport(this, options)](./puppeteer.elementhandle.isintersectingviewport.md) | | Resolves to true if the element is visible in the current viewport. If an element is an SVG, we check if the svg owner element is in the viewport instead. See https://crbug.com/963246. |
|
||||
| [press(key, options)](./puppeteer.elementhandle.press.md) | | Focuses the element, and then uses [Keyboard.down()](./puppeteer.keyboard.down.md) and [Keyboard.up()](./puppeteer.keyboard.up.md). |
|
||||
| [screenshot(this, options)](./puppeteer.elementhandle.screenshot.md) | | This method scrolls element into view if needed, and then uses [Page.screenshot()](./puppeteer.page.screenshot_2.md) to take a screenshot of the element. If the element is detached from DOM, the method throws an error. |
|
||||
| [select(values)](./puppeteer.elementhandle.select.md) | | Triggers a <code>change</code> and <code>input</code> event once all the provided options have been selected. If there's no <code><select></code> element matching <code>selector</code>, the method throws an error. |
|
||||
|
@ -812,15 +812,67 @@ export class ElementHandle<
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves to true if the element is visible in the current viewport.
|
||||
* Resolves to true if the element is visible in the current viewport. If an
|
||||
* element is an SVG, we check if the svg owner element is in the viewport
|
||||
* instead. See https://crbug.com/963246.
|
||||
*/
|
||||
async isIntersectingViewport(
|
||||
this: ElementHandle<Element>,
|
||||
options?: {
|
||||
threshold?: number;
|
||||
}
|
||||
): Promise<boolean>;
|
||||
async isIntersectingViewport(): Promise<boolean> {
|
||||
throw new Error('Not implemented');
|
||||
): Promise<boolean> {
|
||||
const {threshold = 0} = options ?? {};
|
||||
const svgHandle = await this.#asSVGElementHandle(this);
|
||||
const intersectionTarget: ElementHandle<Element> = svgHandle
|
||||
? await this.#getOwnerSVGElement(svgHandle)
|
||||
: this;
|
||||
|
||||
try {
|
||||
return await intersectionTarget.evaluate(async (element, threshold) => {
|
||||
const visibleRatio = await new Promise<number>(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0]!.intersectionRatio);
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(element);
|
||||
});
|
||||
return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold;
|
||||
}, threshold);
|
||||
} finally {
|
||||
if (intersectionTarget !== this) {
|
||||
await intersectionTarget.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if an element is an SVGElement (included svg, path, rect
|
||||
* etc.).
|
||||
*/
|
||||
async #asSVGElementHandle(
|
||||
handle: ElementHandle<Element>
|
||||
): Promise<ElementHandle<SVGElement> | null> {
|
||||
if (
|
||||
await handle.evaluate(element => {
|
||||
return element instanceof SVGElement;
|
||||
})
|
||||
) {
|
||||
return handle as ElementHandle<SVGElement>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async #getOwnerSVGElement(
|
||||
handle: ElementHandle<SVGElement>
|
||||
): Promise<ElementHandle<SVGSVGElement>> {
|
||||
// SVGSVGElement.ownerSVGElement === null.
|
||||
return await handle.evaluateHandle(element => {
|
||||
if (element instanceof SVGSVGElement) {
|
||||
return element;
|
||||
}
|
||||
return element.ownerSVGElement!;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -747,25 +747,6 @@ export class CDPElementHandle<
|
||||
|
||||
return imageData;
|
||||
}
|
||||
|
||||
override async isIntersectingViewport(
|
||||
this: CDPElementHandle<Element>,
|
||||
options?: {
|
||||
threshold?: number;
|
||||
}
|
||||
): Promise<boolean> {
|
||||
const {threshold = 0} = options ?? {};
|
||||
return await this.evaluate(async (element, threshold) => {
|
||||
const visibleRatio = await new Promise<number>(resolve => {
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
resolve(entries[0]!.intersectionRatio);
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(element);
|
||||
});
|
||||
return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold;
|
||||
}, threshold);
|
||||
}
|
||||
}
|
||||
|
||||
function computeQuadArea(quad: Point[]): number {
|
||||
|
14
test/assets/inline-svg.html
Normal file
14
test/assets/inline-svg.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<svg>
|
||||
<circle cx="10" cy="10" r="10" />
|
||||
</svg>
|
||||
|
||||
<div style="margin-top: 5000px;">
|
||||
<svg>
|
||||
<circle cx="10" cy="10" r="10" />
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -486,6 +486,56 @@ describe('ElementHandle specs', function () {
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
it('should work with svg elements', async () => {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
await page.goto(server.PREFIX + '/inline-svg.html');
|
||||
const visibleCircle = await page.$('circle');
|
||||
const visibleSvg = await page.$('svg');
|
||||
expect(
|
||||
await visibleCircle!.isIntersectingViewport({
|
||||
threshold: 1,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
await visibleCircle!.isIntersectingViewport({
|
||||
threshold: 0,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
await visibleSvg!.isIntersectingViewport({
|
||||
threshold: 1,
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
await visibleSvg!.isIntersectingViewport({
|
||||
threshold: 0,
|
||||
})
|
||||
).toBe(true);
|
||||
|
||||
const invisibleCircle = await page.$('div circle');
|
||||
const invisibleSvg = await page.$('div svg');
|
||||
expect(
|
||||
await invisibleCircle!.isIntersectingViewport({
|
||||
threshold: 1,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
await invisibleCircle!.isIntersectingViewport({
|
||||
threshold: 0,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
await invisibleSvg!.isIntersectingViewport({
|
||||
threshold: 1,
|
||||
})
|
||||
).toBe(false);
|
||||
expect(
|
||||
await invisibleSvg!.isIntersectingViewport({
|
||||
threshold: 0,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom queries', function () {
|
||||
|
Loading…
Reference in New Issue
Block a user