The holdable component allows entities in A-Frame VR scenes to be picked up, held, and released using hand controllers with raycasters. It supports dynamic positioning and rotation offsets, automatic physics handling during grab and release, and custom collision shape preservation.
This component is ideal for VR scenes where users interact with objects, such as picking up tools, throwing props, or manipulating items in a puzzle or simulation.
Tested with A-Frame 1.7.0
Have a headset? Try it out in VR!
Add the holdable component to an entity you'd like to make grabbable, such as a model or primitive geometry. Optionally provide local position and rotation offsets:
<a-scene post-model-load-refresh>
<!-- Holdable Entity: Basketball -->
<a-entity gltf-model="#basketball" holdable></a-entity>
<!-- Holdable Entity: Sword -->
<a-entity gltf-model="#sword" holdable="position: 0 0.1 -0.1; rotation: 0 180 0"></a-entity>
<!-- Controllers -->
<a-entity id="rightHand" oculus-touch-controls="hand: right" raycaster="objects: .interactable"></a-entity>
<a-entity id="leftHand" oculus-touch-controls="hand: left" raycaster="objects: .interactable"></a-entity>
</a-scene>| Parameter | Type | Description | Default | Options |
|---|---|---|---|---|
| position | vec3 | Local offset position where the object should be held (relative to controller). | 0 0 0 |
Any position coordinates (in meters) |
| rotation | vec3 | Local offset rotation applied when held (relative to controller). | 0 0 0 |
Any rotation angles (in degrees) |
| leftHandRotationInvert | array | If using custom position or rotation, choose the axes to invert for left hand. | y, z |
x, y, z (comma-separated) |
| insideMeshDetection | boolean | Enables detection of controller inside mesh by making materials double-sided (impacts performance). | true |
true, false |
| debug | boolean | Outputs the relative position and rotation of held objects. Useful for setting custom offsets. | false |
true, false |
Notes:
- Left-hand interactions mirror the right hand - position and rotation offsets are automatically flipped when grabbing with the left hand. But you can customize the inverted axes with
leftHandRotationInvert. - Using "0 0 0" for position or rotation will indicate no custom position or rotation. For rotation, this means the rotation will be the same as the object's original rotation when grabbed.
- For easiest setup, enable
debug: trueand grab the object naturally. Then copy values from console (via dev tools or vr-logger component).
- Raycaster Detection: Object becomes interactable via raycaster when intersected.
- Controller Grip: On grip down, object attaches to the controller.
- Physics Preservation: Saves and restores original physics settings, including dynamic-body, ammo-body, and shape-based Cannon.js configurations.
- Throw Velocity: Upon release, a velocity is applied based on the controller's movement.
- Rotation Center: Rotates around the controller (pivot), not the object's center.
- Grab Offsets: Supports:
- Per-object local grab offsets via
positionandrotation - Global default via
data-holdable-grab-positionset on<a-scene>(see below) - Fallback to automatically computed grab position/rotation if not specified (model does not move when grabbed)
- Per-object local grab offsets via
- You don't need to manually add the intersection class (
.interactable) -holdableadds it automatically. - It's recommended to use the
post-model-load-refreshcomponent (part ofmss-aframe-kit) if working with GLTF models to ensure raycasters and physics bodies are refreshed post-load.
- Grip/Release Property Modifiers: Use the
holdable-grip-andholdable-release-prefixes to apply component modifications on grip and release, respectively. This allows you to change properties like color, scale, or even apply animations when the object is held or released (e.g.holdable-grip-material="color: blue; opacity: .1"). - Intersection Class Customization: Use
data-holdable-intersection-classon<a-scene>to change the default.interactableclass used for raycaster targeting. - Custom Shape Collision Support: Preserves and restores multiple
shape__*components with their configs (Learn More). - Supports Sleepy Physics Objects: The
sleepycomponent from theaframe-physics-extraslibrary can be used to reduce jitter for objects that are stacked (Learn More). - Dynamic Body Handling: If
holdable-dynamic-bodyattribute is added to model entity, addsdynamic-bodyon release. This way, it could be astatic-bodyor have no physics initially (perfect for something like a hanging picture frame). Theholdable-dynamic-bodycan accept additional properties thatdynamic-bodyuses, likemass,linearDamping, etc., to customize the physics behavior when the object is grabbed.
- No Two-Hand Support: Only one controller can hold an object at a time.
- Ammo.js Support is Incomplete: Collisions do not behave correctly after releasing an object with Ammo.js physics enabled.
- No Physics While Held: Held objects are removed from the physics simulation, so they cannot collide with or affect other dynamic objects while being held.
- No Custom Left Hand Positioning: Left hand position/rotation cannot be precisely customized at this time. However, you can invert the right hand positioning with
leftHandRotationInvert, which works for most objects.
Feel free to reach out to me at Making Spider Sense for any questions!