← ALL NOTES
METHOD·2026.09·5 MIN READ
Local Re-rendering for a Touch Magnifier in a 3D Scene
3d-visualization3d-design
The Problem

A common way to build a touch magnifier is to capture the current screen and enlarge that image above the finger.

That works well in a 2D interface, but it behaves differently in a perspective 3D scene. Enlarging a screenshot only scales pixels that have already been rendered. It does not recalculate the camera projection, so the result feels like a stretched image rather than a closer view of the scene.

I ran into this while building a measurement-duplication prototype. On touch devices, long-pressing and dragging a measurement endpoint or label needed a circular magnifier above the finger, enlarged by roughly 1.3x, so the user could see exactly where the endpoint would land.

The scope was also defined up front: the magnifier would re-render the Three.js scene itself — model geometry, measurement lines, and endpoints — but not HTML labels. Those labels live in a separate DOM layer. Including them would require duplicating and remapping them into the magnifier's local coordinate system, which was not necessary for the core task.

Re-rendering a Local View

The final implementation does not enlarge a screenshot. It uses the camera's setViewOffset() to re-render a small region around the touch point with the same scene and projection model.

During dragging, each frame follows roughly this sequence:

  1. calculate a crop region around the current touch point in the main viewport;
  2. pass that region to setViewOffset() and update the projection matrix;
  3. render the same scene and camera into a separate canvas of about 140 x 140px;
  4. clear the view offset immediately after rendering and restore the projection matrix before the main scene renders again.

The small canvas is clipped into a circle and styled with a border, shadow, and center crosshair. It sits above the touch point so the finger does not cover the area being inspected.

The important part is that the magnifier is not showing a scaled copy of the main frame. It is showing the same 3D scene projected again for a smaller local viewport. The perspective relationship between model geometry, endpoints, and camera therefore remains consistent with the main view.

Matching the Aspect Ratio

The first implementation used the right rendering approach but still produced visible distortion.

The crop size was initially calculated as the main viewport width divided by 1.3 and the main viewport height divided by 1.3. Because the phone viewport was portrait-oriented, the crop region kept that same rectangular aspect ratio.

The magnifier target, however, was a 140 x 140px square canvas.

That meant a rectangular camera region was being mapped into a square render target. The result was stretched geometry: endpoints lost detail and could turn into large flat circles while dragging.

The fix was to make the crop region square as well. The smaller side of the main viewport became the base dimension, and the magnification factor was applied to that value.

Once the source viewport and render target shared the same aspect ratio, model edges, textures, and endpoint details rendered correctly.

The practical rule is simple: source viewport aspect ratio = render-target aspect ratio.

A correct 3D projection can still look wrong if the final 2D mapping stretches it into a different shape.

Touch-only Lifecycle

The magnifier exists only for the touch interaction path.

It is created and shown when touch enters the long-press drag flow. Mouse interaction never calls the magnifier logic, so desktop does not need to create the extra DOM or WebGL rendering resources.

This keeps the feature out of the generic input path instead of repeatedly asking at runtime whether the current device should use it.

Scope of the Effect

HTML labels were intentionally left outside the magnifier.

Three.js content can be rendered again through the camera. DOM labels cannot. Including them would require an additional system for cloning labels, converting their positions into the magnifier's local coordinate system, and keeping them synchronized with the second view.

For this task, the user mainly needed to judge the endpoint against the model surface. Reading the label text inside the magnifier was not essential, so the added complexity was not justified.

This decision was made before implementation rather than after the work had already expanded. For effects that cross WebGL and DOM layers, agreeing on the required visual scope early helps keep the implementation aligned with the actual task.

Takeaways
  1. Re-render 3D content instead of enlarging a screenshot. If the goal is to make the scene feel closer, the camera projection should be recalculated rather than the final pixels being scaled.
  2. Match the local viewport to the render target's aspect ratio. Correct projection alone does not prevent distortion if the final 2D mapping changes the shape.
  3. Create input-specific features only on the input path that needs them. A touch-only magnifier does not need to exist in the desktop lifecycle.
  4. Define the WebGL / DOM scope before implementation. Whether HTML overlays need to be included should depend on the task, not on visual completeness alone.
About Prototype Testing

The technical direction came primarily from Claude Code; my role was to verify whether it matched the intended interaction and whether the result held up on real devices.

The main issue did not come from choosing the wrong rendering method. It came from a concrete dimensional mismatch: the source crop and the target canvas had different aspect ratios.

That is why runnable prototypes are useful for this kind of 3D UI work. The rendering model can be correct in principle, while dimensions, ratios, and real touch behavior still need to be tested in motion.

CONTINUE READING
Translating Figma Component Properties into Code Props2026.09Engineering a Figma Design System into Production Assets2026.09