Zoom lens diagram - Copyright 2026 vHW
by PhilQ
HTML
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="-200 0 800 100" fill="none" id="svg">
<!-- <rect y="50" x="50" width="50" height="50" fill="#ff0000"/> -->
<g id="rays"></g>
</svg>
<div class="controls">
<div class="input">
<label>Zoom</label>
<input type="range" id="zoom" value="44" min="18" max="70" step="1">
</div>
</div>
SCSS
body {
background-color: #eee;
}
.controls {
display: flex;
width: 100%;
max-width: 1024px;
margin: 2rem auto 0;
flex-direction: row;
gap: 1rem;
background: #ddd;
padding: 1rem;
}
.input {
width: 25%;
display: flex;
flex-direction: column;
gap: 0.5rem;
justify-content: flex-start;
align-items: stretch;
}
JavaScript
const degToRad = (deg) => {
return (deg * Math.PI) / 180.0;
}
const radToDeg = (rad) => {
return (rad * 180.0) / Math.PI;
}
function easeOutSine(x) {
return Math.sin((x * Math.PI) / 2);
}
function easeInSine(x) {
return 1 - Math.cos((x * Math.PI) / 2);
}
function easeInOutSine(x) {
return -(Math.cos(Math.PI * x) - 1) / 2;
}
const createTag = (tag, props = {}, content = '') => {
const ns = 'http://www.w3.org/2000/svg';
const el = document.createElementNS(ns, tag);
if (false && 'svg' === tag) {
el.setAttributeNS(
'http://www.w3.org/2000/xmlns/',
'xmlns:xlink',
'http://www.w3.org/1999/xlink'
);
}
for (const prop in props) {
el.setAttribute(prop, props[prop]);
}
if (content?.length) {
el.textContent = content;
}
return el;
}
const refractive_index_air = 1.000273; // Air
const refractive_index_glass = 1.458; // Quartz
/**
* Intersect a left-going ray with a circular arc and compute:
* - intersection point
* - incidence angle (relative to the surface normal)
* - refraction angle using Snell's law
*
* Coordinate system:
* - +x = right
* - +y = down/up does not matter as long as consistent
* - angles in degrees
* - 180° = straight left
* - 135° = top-left
* - 225° = bottom-left
*
* Arc assumptions:
* - The arc is part of a circle
* - It is symmetric around the x-axis through center.y
* - "convex" => using the RIGHT half of the circle
* - "concave" => using the LEFT half of the circle
* - height is the total visible arc height
*
* Returns:
* {
* point: { x, y },
* incidenceAngleDeg,
* refractionAngleDeg,
* normal: { x, y },
* refractedDirection: { x, y }
* }
*
* or false if no valid intersection exists.
*/
function rayArcIntersection({
rayX,
rayY,
rayAngleDeg,
centerX,
centerY,
radius,
sag, // optional, only used for extra clipping
convex, // true =...