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

> Learn how ego-browser uses the elementCenter calculation for precise screenshot clipping. Discover its role in anchoring clip regions for Chrome DevTools Protocol commands.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-16

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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:

```js
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`:

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

```

Capture a screenshot clipped around that center point:

```js
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:

```js
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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/observe.ts) – implements `elementCenter` and the screenshot clipping logic.
- [`src/element-resolver.js`](https://github.com/citrolabs/ego-lite/blob/main/src/element-resolver.js) – contains `resolveElementCenter`, which runs the CDP `Runtime.evaluate` script.
- [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) – re-exports `elementCenter` so it is available as `page.elementCenter`.
- [`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts) – documents the helper signature and usage examples.
- [`src/driver/pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/pointer.ts) for mouse targeting.