What Is the elementCenter Calculation Used for Screenshot Clipping in ego-browser?

The elementCenter helper in ego-browser resolves a CSS selector or @ref to the viewport-relative center point of a DOM element, and that coordinate becomes the geometric anchor for the clip region passed to Chrome DevTools Protocol's screenshot command.

The elementCenter calculation is the geometric foundation for screenshot clipping in ego-browser, the lightweight browser-automation layer inside the citrolabs/ego-lite repository. When you want to capture a specific element rather than the full viewport, this helper locates the element's visual center. The resulting coordinates are then consumed by the screenshot routine in src/driver/observe.ts to build a pixel-perfect clipping rectangle.

How elementCenter Computes the Viewport Center Point

Resolving References with ensureRefMapForRef

Inside src/driver/observe.ts, the elementCenter method first ensures the internal ref-map is current by calling ensureRefMapForRef. This guarantees that a numeric @ref handle can be safely resolved to a live DOM element before any geometry is measured.

The resolveElementCenter CDP Script

The actual measurement is delegated to resolveElementCenter in src/element-resolver.js. That routine runs a CDP Runtime.evaluate script which:

  • Retrieves the element's bounding box via getBoundingClientRect().
  • Extracts x, y, width, and height in CSS pixels.
  • Computes the center point as:
const cx = rect.x + rect.width / 2;
const cy = rect.y + rect.height / 2;

The function returns an object { x: cx, y: cy } representing the point relative to the current viewport after all scroll offsets. According to the ego-lite source code, this viewport-relative precision is what makes downstream clipping reliable.

How That Calculation Enables Screenshot Clipping

Building the Clip Region from a ScreenshotClip

When observe.screenshot is invoked, the caller may supply a ScreenshotClip object, or the code can build one automatically from an element. In either case, the coordinates derived from elementCenter serve as the geometric anchor for the x and y bounds.

Applying Device Pixel Ratio Scaling

Before the capture is triggered, observe.screenshot reads window.devicePixelRatio and computes a CSS scale factor of 1 / dpr. It then merges scale: cssScale with the user-supplied x, y, width, and height. This step ensures that high-DPI displays do not distort the clipped region.

Capturing via Page.captureScreenshot

The finalized params.clip object, now containing both the geometric bounds and the scale factor, is passed to the CDP method Page.captureScreenshot. Because the clip coordinates originate from the elementCenter calculation, the resulting PNG is aligned exactly with the element's visual representation on the page.

Practical Examples for Element-Level Screenshots

Resolve the center of an element by selector or @ref:

const { x, y } = await page.elementCenter('#login-button');
// → { x: 452.3, y: 298.7 }

Capture a screenshot clipped around that center point:

const clip = {
  x: x - 100,
  y: y - 50,
  width: 200,
  height: 100
};

const path = await page.screenshot({ clip });
// → writes a PNG containing only the login-button area

For a tight clip that uses the element's exact dimensions:

const rect = await page.evaluate((sel) => {
  const el = document.querySelector(sel);
  const { x, y, width, height } = el.getBoundingClientRect();
  return { x, y, width, height };
}, '#login-button');

const tightPath = await page.screenshot({ clip: { ...rect } });

Key Source Files in the ego-lite Codebase

  • src/driver/observe.ts – implements elementCenter and the screenshot clipping logic.
  • src/element-resolver.js – contains resolveElementCenter, which runs the CDP Runtime.evaluate script.
  • src/helpers.ts – re-exports elementCenter so it is available as page.elementCenter.
  • src/format.ts – documents the helper signature and usage examples.
  • src/driver/pointer.ts – imports elementCenter for mouse-target resolution, demonstrating broader use beyond screenshots.

Summary

  • elementCenter in ego-browser resolves a selector or @ref to a viewport-relative center point via CDP.
  • The calculation uses getBoundingClientRect() data and the formula rect.x + rect.width / 2 and rect.y + rect.height / 2.
  • src/driver/observe.ts consumes this point to build a clip object for Page.captureScreenshot.
  • The screenshot routine compensates for device pixel ratio with a scale factor of 1 / dpr.
  • Because the clip is anchored to the element's center, the captured image aligns precisely with the element's on-screen bounds.

Frequently Asked Questions

How does elementCenter handle scrolled pages?

The CDP script evaluated by resolveElementCenter calls getBoundingClientRect(), which returns coordinates relative to the current viewport after any scrolling. Therefore, elementCenter naturally accounts for scroll offset without requiring manual math.

Can elementCenter accept numeric @ref handles instead of CSS selectors?

Yes. The elementCenter implementation in src/driver/observe.ts first invokes ensureRefMapForRef to map a numeric @ref to a live element, so both string selectors and internal reference handles are valid inputs.

Why is device pixel ratio important for screenshot clipping?

observe.screenshot fetches window.devicePixelRatio and applies a CSS scale of 1 / dpr to the clip parameters. Without this adjustment, high-DPI displays would produce screenshots with incorrect physical dimensions or blurry scaling.

Where is the elementCenter helper exposed on the page object?

src/helpers.ts re-exports elementCenter, attaching it to the page instance so you can call it directly as await page.elementCenter('#selector'). The same utility is also imported in src/driver/pointer.ts for mouse targeting.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →