component 19 / 28WORKING
SC Ruler
- type
- Code Component
- platform
- Framer
- license
- SecretoCho Design
demo · runs with JavaScript
High-DPI canvas ruler with px/in/cm/mm units, zoom, offset, pointer read-out and a drag-to-measure mode.
SCRuler draws a measurement ruler on a high-DPI canvas: major and minor ticks, labels in px, inches, cm or mm, zoom and offset, normal or reversed direction, horizontal or vertical. Move the pointer over it and a guide line with the value follows; turn on Measurement Mode and drag to read a distance.
Made as a HUD element for the old site’s pages, it fills whatever frame it sits in and redraws on resize.
- Utilities
- Debug
- Visual
Main
Overview
- Built With
- React (hooks), Canvas 2D, ResizeObserver, Framer Code Component API
- Layout Support
- Fills the frame (min 200 × 40 px); redraws on resize
- Orientation
- Horizontal or vertical; normal or reversed direction
- Ideal Use Case
- Blueprint-style page chrome, design-tool mockups, measuring overlays in prototypes
Tags
Code comment
// SC_Ruler.tsx — High-DPI Canvas Ruler for Framer with Measurement ModeDependencies
- Technologies
- None (Canvas 2D + ResizeObserver)
- Notes
- Resolves var(--…) colours through getComputedStyle on documentElement, then body and a Framer component element; a fallback in the var() is returned at the first step.
Complexity
- Logic Level
- Medium (unit maths, tick loop, two interaction modes, token colour resolution)
- Runtime Cost
- Low (one requestAnimationFrame per change; no continuous loop; tick loop capped at 2,000 steps)
- Component Size
- ~28 KB TSX (28,218 bytes, 947 lines)
- Notes
- Draws at max(devicePixelRatio, 2) for crisp one-pixel ticks.
Guide
Instructions
- Drop SCRuler into a frame at least 200 × 40 px.
- Basic: choose the axis, the direction (Normal → or Reverse ←) and the unit.
- Scale: zoom, offset, pixels per unit (px mode) and screen DPI (in / cm / mm modes).
- Ticks: major interval, subdivisions, and both tick lengths as a percentage of the ruler’s thickness.
- Turn Show Labels on, set Label Every N, padding and the label font; set the four colours.
- Pointer draws a guide and value on hover; Measurement Mode lets the visitor drag a distance.
Examples / Presets
Default
The source defaults: horizontal, px, a major tick every 100 with 9 subdivisions, ticks 30 % / 15 %, labels on, a dashed blue pointer, measurement off.
Centimetres
- basic.unit
- cm
- ticks.majorInterval
- 1
- ticks.minorDivisions
- 9 (millimetre subdivisions at 96 dpi)
Inches
- basic.unit
- in
- ticks.majorInterval
- 1
- ticks.minorDivisions
- 7
Measure (site)
- basic.axis
- vertical
- ticks.majorLength
- 10
- ticks.minorLength
- 5
- pointer.color
- rgb(0, 250, 146)
- measurement.enabled
- true
- measurement.color
- #FF3366 (the old site’s contact page)
Sidebar (site)
- basic.axis
- vertical
- ticks.majorLength
- 10
- ticks.minorLength
- 5
- showLabels
- false
- pointer.enabled
- false
- measurement.enabled
- false (a 96 px strip on the old site’s services and about pages, ticks in the B-W 20 / 10 / 24 % tokens)
Mechanics
How it Works
- Scale: pixelsPerUnit = (px ? Pixels/Unit : dpi per unit) × zoom; unitToPx maps in → dpi, cm → dpi / 2.54, mm → dpi / 25.4.
- Canvas: a ResizeObserver tracks the container (min 200 × 40); the canvas is sized at max(devicePixelRatio, 2) and the context scaled back, so ticks land on device pixels.
- Ticks: step = major / (subdivisions + 1); the loop starts two steps before the offset and stops at 2,000 steps; every tick is drawn at Math.round(pos) + 0.5 for a crisp 1 px line; majors every (subdivisions + 1) steps get a label when their index divides by Label Every N.
- Labels: px are rounded; mm show 0 or 1 decimal; in / cm show 2 decimals under 10, else 1.
- Pointer: on hover a guide line (solid or dashed, 70 % alpha) crosses the ruler and a filled tooltip shows the value at the pointer.
- Measurement: mousedown starts a line, the pointer extends it, mouseup or leaving ends it; the label shows the distance along the ruler’s axis in the current unit.
- Colours: resolveFramerColor turns Framer colour objects (r g b a / h s l a) and var(--token) strings into CSS colours by reading computed styles.
Performance & Tuning
- The ruler redraws once per state change (one requestAnimationFrame), not continuously; pointer moves trigger a redraw each.
- Very small steps (tiny major interval × many subdivisions × high zoom) hit the 2,000-step cap; raise the interval or lower the subdivisions.
- At 2× or more DPR a full-width ruler is a large canvas; keep the frame as thin as the design allows.
Compatibility & Limits
- Evergreen browsers with Canvas 2D and ResizeObserver; requests a desynchronized 2D context where supported.
- Mouse events only: there are no touch handlers, so the pointer read-out and measuring do not work on touch screens.
- Inches, cm and mm depend on the Screen DPI value, not the real screen.
- Reverse ← draws ticks only in the first two steps (about 20 px at the defaults), and with an offset its pointer value disagrees with the labels.
- On a vertical ruler the pointer label is drawn left-aligned from its box’s centre, so it spills out of the box.
- A vertical ruler is still at least 200 px wide; tick lengths are capped at 50 px (major) and 30 px (minor).
Quality
Accessibility
- The canvas has no text alternative or role: treat it as decorative (aria-hidden on the layer) unless the measurement matters.
- Labels are drawn pixels; they do not scale with browser text size.
Security & Privacy
- No network calls, no storage, no permissions, no third-party libraries.
- Reads getComputedStyle on documentElement first to resolve var(--token) colours (when the var() has a fallback it returns the fallback at once and never reaches body or the component element); writes nothing to the DOM outside its own canvas.
- Mouse coordinates are read from React events only.
Reference
Props
| name | type | default | description |
|---|---|---|---|
basic | object | axis=horizontal, direction=clockwise, unit=px | Basic. Axis horizontal | vertical; Direction Normal → (clockwise) | Reverse ← (counter-clockwise); Unit Pixels | Inches | CM | MM. |
scale | object | zoom=1, offset=0, pixelsPerUnit=1, dpi=96 | Scale. Zoom 0.1–10; Offset −10000–10000; Pixels/Unit 0.1–100 (px mode); Screen DPI 72–300 (physical units). |
ticks | object | majorInterval=100, minorDivisions=9, majorLength=30, minorLength=15 | Ticks. Major Every 1–1000 units; Subdivisions 0–20; Major / Minor Length 10–100 % and 5–100 % of the ruler’s thickness. |
showLabels | boolean | true | Show Labels. |
labelFrequency | number | 1 | Label Every N. Label every Nth major tick, 1–10 (shown when labels are on). |
labelPadding | number | 4 | Label Padding. 0–20 px between tick and label. |
font | FontStyle | -apple-system … system-ui, sans-serif · 11 · 400 · normal | Font. Label typeface (shown when labels are on). |
colors | object | background=transparent, majorTick=#000000, minorTick=rgba(0, 0, 0, 0.3), text=#000000 | Colors. Background, Major Ticks, Minor Ticks, Text; Framer colour tokens are resolved. |
pointer | object | enabled=true, color=#0066FF, lineWidth=1, lineStyle=dashed | Pointer. Hover guide line and value tooltip; width 0.5–5; Solid | Dashed. |
measurement | object | enabled=false, color=#FF3366, showDelta=true | Measurement Mode. Enable Measure (drag to measure), Measure Color, Show Distance label. |
The Basic group’s axis, direction and unit controls have no defaultValue in the control tree; their defaults come from SCRuler.defaultProps.
Notes & Tips
- For a drawing-style ruler set unit to cm, Major Every 1 and Subdivisions 9: the minors are millimetres.
- Offset shifts the zero point in pixels.
- Colours are resolved once per change of the colours prop, and a var(--token) with a fallback usually comes out as the fallback, so they do not follow a theme switch.
Troubleshooting / FAQ
The canvas says “Configure ruler settings”
The computed step is zero or not finite: check Major Every, Subdivisions, Zoom and Pixels/Unit.
Labels overlap
Raise Label Every N or the major interval, or lower the zoom.
Nothing happens on a phone
The pointer and measurement handlers are mouse events only.
Token colours come out black
With a fallback in the var() you get the fallback colour; black only when there is no fallback and nothing resolves (the canvas rejects the raw var() string). Use a literal colour.
Source
Header comment
// SC_Ruler.tsx — High-DPI Canvas Ruler for Framer with Measurement ModeKey excerpt
The tick loop: each tick lands on a half-pixel for a crisp one-device-pixel line, majors fall every (subdivisions + 1) steps, and anything outside the visible axis is skipped.
// Draw ticks ctx.lineWidth = 1 const numSteps = Math.min(Math.ceil(axisLength / stepPx) + 10, 2000) const majorsEvery = ticks.minorDivisions > 0 ? ticks.minorDivisions + 1 : 1 for (let i = 0; i < numSteps; i++) { const tickIdx = startIdx + i const logicalPx = tickIdx * stepPx const viewPos = dir * logicalPx - scale.offset if (viewPos < -stepPx * 2 || viewPos > axisLength + stepPx * 2) continue const isMajor = ticks.minorDivisions === 0 || tickIdx % majorsEvery === 0 const tickLen = isMajor ? majorTickLength : minorTickLength const pos = Math.round(viewPos) + 0.5 // Draw tick ctx.strokeStyle = isMajor ? resolvedColors.majorTick : resolvedColors.minorTick ctx.beginPath() if (isHorizontal) { ctx.moveTo(pos, 0) ctx.lineTo(pos, tickLen) } else { ctx.moveTo(0, pos) ctx.lineTo(tickLen, pos) } ctx.stroke()Credits
Credits
© SecretoCho Design
- license
- SecretoCho Design
SecretoCho Design
Disclaimer
Provided “as is” for visual/UX purposes. Validate performance, accessibility, and licensing within your project.
Proves
Hiring a design engineer?
I’m open to senior design engineer roles: remote from Belgrade, or relocation with visa sponsorship. Write to me, or read the CV first. Client work stays selective, one project at a time.
sc@secretocho.design