SecretoCho
menu

component 19 / 28WORKING

SC Ruler

type
Code Component
platform
Framer
license
SecretoCho Design
needs JavaScript

demo · runs with JavaScript

fig 00 · live demo, ported from SC_Ruler.tsx

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

  • ruler
  • canvas
  • measurement
  • ticks
  • hud
  • units
  • dpi
  • blueprint

Code comment

SC_Ruler.tsx · lines 1–1 · the file’s header comment
// SC_Ruler.tsx — High-DPI Canvas Ruler for Framer with Measurement Mode

Dependencies

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

  1. Drop SCRuler into a frame at least 200 × 40 px.
  2. Basic: choose the axis, the direction (Normal → or Reverse ←) and the unit.
  3. Scale: zoom, offset, pixels per unit (px mode) and screen DPI (in / cm / mm modes).
  4. Ticks: major interval, subdivisions, and both tick lengths as a percentage of the ruler’s thickness.
  5. Turn Show Labels on, set Label Every N, padding and the label font; set the four colours.
  6. 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

nametypedefaultdescription
basicobjectaxis=horizontal, direction=clockwise, unit=pxBasic. Axis horizontal | vertical; Direction Normal → (clockwise) | Reverse ← (counter-clockwise); Unit Pixels | Inches | CM | MM.
scaleobjectzoom=1, offset=0, pixelsPerUnit=1, dpi=96Scale. Zoom 0.1–10; Offset −10000–10000; Pixels/Unit 0.1–100 (px mode); Screen DPI 72–300 (physical units).
ticksobjectmajorInterval=100, minorDivisions=9, majorLength=30, minorLength=15Ticks. Major Every 1–1000 units; Subdivisions 0–20; Major / Minor Length 10–100 % and 5–100 % of the ruler’s thickness.
showLabelsbooleantrueShow Labels.
labelFrequencynumber1Label Every N. Label every Nth major tick, 1–10 (shown when labels are on).
labelPaddingnumber4Label Padding. 0–20 px between tick and label.
fontFontStyle-apple-system … system-ui, sans-serif · 11 · 400 · normalFont. Label typeface (shown when labels are on).
colorsobjectbackground=transparent, majorTick=#000000, minorTick=rgba(0, 0, 0, 0.3), text=#000000Colors. Background, Major Ticks, Minor Ticks, Text; Framer colour tokens are resolved.
pointerobjectenabled=true, color=#0066FF, lineWidth=1, lineStyle=dashedPointer. Hover guide line and value tooltip; width 0.5–5; Solid | Dashed.
measurementobjectenabled=false, color=#FF3366, showDelta=trueMeasurement 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 · lines 1–1 · header comment
// SC_Ruler.tsx — High-DPI Canvas Ruler for Framer with Measurement Mode

props [REF]

Key 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.

SC_Ruler.tsx · lines 370–402 · tsx
        // 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

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

read the CV · LinkedIn