Skip to content

MeasuringTapeNode ​

MeasuringTapeNode (from scenerystack/scenery-phet) draws a classic tape-measure image with two independently draggable ends — a base (the case) and a tip (the free end of the tape) — connected by a line, with a text readout showing the distance between them in whatever units you supply. Dragging the base moves the whole tape (tip included); dragging the tip alone extends or retracts the tape. It's the standard "let the user measure something" tool across PhET simulations, distinct from RulerNode, which is a rigid ruler graphic rather than a two-point tape.

ts
import { MeasuringTapeNode } from 'scenerystack/scenery-phet';
import { Property } from 'scenerystack/axon';

A minimal example ​

ts
const unitsProperty = new Property( { name: 'cm', multiplier: 100 } );

const measuringTapeNode = new MeasuringTapeNode( unitsProperty, {
  tandem: tandem.createTandem( 'measuringTapeNode' ),
  dragBounds: layoutBounds.eroded( 20 ) // model-coordinate Bounds2 the tape stays within
} );

// Position it explicitly (base at model origin, tip 1 model unit to the right)
measuringTapeNode.basePositionProperty.value = new Vector2( 0, 0 );
measuringTapeNode.tipPositionProperty.value = new Vector2( 1, 0 );

The multiplier in unitsProperty's value converts the internal model-coordinate distance into display units before formatting the readout — a multiplier: 100 reading of 0.5 model units displays as 50 cm.

Constructor ​

ts
new MeasuringTapeNode(
  unitsProperty: TReadOnlyProperty<MeasuringTapeUnits>,
  providedOptions?: MeasuringTapeNodeOptions
)

where MeasuringTapeUnits = { name: string; multiplier: number }.

Options ​

OptionDefaultEffect
basePositionPropertynew Vector2Property( (0,0) )Model-coordinate position of the base (case); supply your own to share it with model code
tipPositionPropertynew Vector2Property( (1,0) )Model-coordinate position of the tip
hasValuetrueSet false to hide the text readout entirely (useful when building an icon)
dragBoundsBounds2.EVERYTHINGModel-coordinate bounds constraining the base (and, if isTipDragBounded, the tip)
modelViewTransformidentityMust have equal x/y scale factors — asserted at construction
significantFigures1Digits shown in the distance readout
isTipDragBoundedtrueWhether the tip is also constrained to dragBounds, or free to roam anywhere
interactivetrueSet false to disable the built-in drag/keyboard-drag listeners, e.g. when using MeasuringTapeNode.createIcon()
baseScale0.8Scales the base image, and with it the crosshair/text layout that's positioned relative to it
textColor / textBackgroundColor'white' / nullReadout text and background colors
baseDragStarted / baseDragEndedno-opsCallbacks fired when a base drag starts/ends — handy for "dropped into toolbox" detection
phetioFeaturedMeasuredDistancePropertyfalseWhether measuredDistanceProperty is marked phetioFeatured

Instance properties ​

PropertyTypeDescription
measuredDistancePropertyTReadOnlyProperty<number>Model-coordinate distance between base and tip, as a DerivedProperty
basePositionProperty / tipPositionPropertyProperty<Vector2>Live positions, read-write unless you passed your own
isBaseUserControlledProperty / isTipUserControlledPropertyTReadOnlyProperty<boolean>Whether the user is actively dragging that end right now
modelViewTransformPropertyProperty<ModelViewTransform2>Wraps the modelViewTransform option; update it to re-project the tape live

Methods ​

MethodEffect
reset()Resets basePositionProperty/tipPositionProperty — only the ones this Node created itself (see warning below)
setDragBounds( bounds ) / getDragBounds()Changes the model-coordinate drag bounds, immediately clamping the current base (and tip, if bounded) into them
startBaseDrag( event )Programmatically begins a base drag from a Scenery input event
getLocalBaseCenter() / getLocalBaseBounds()Base image geometry in the Node's local coordinate frame
MeasuringTapeNode.createIcon( options? ) (static)Builds a non-interactive, valueless snapshot Node suitable for a toolbox icon

reset() and dispose() only touch Properties MeasuringTapeNode created itself

If you pass your own basePositionProperty/tipPositionProperty (so your model can read them directly), MeasuringTapeNode does not reset or dispose them — you own their lifecycle. reset()/dispose() only reset/dispose the default Vector2Property instances it constructs internally when you don't supply your own. Forgetting this is a common source of "reset all doesn't move the tape back" bugs when a model-owned position Property is involved.