Events
Ripl provides a full event system modeled after the browser DOM. Elements can listen for and emit events, events bubble up through the element hierarchy, and propagation can be stopped — all familiar patterns for web developers.
Demo
Hover over, click, and drag the elements to see events in action.
NOTE
For the full API, see the Core API Reference.
EventBus
Every element in Ripl extends EventBus, which provides the core event subscription and emission API.
on(event, handler, options?)
Subscribe to an event. Returns an unsubscribe function:
const off = circle.on('click', (event) => {
console.log('Clicked!', event.data);
});
// Later, unsubscribe
off();once(event, handler)
Subscribe to an event that fires only once:
circle.once('click', (event) => {
console.log('First click only');
});off(event?, handler?)
Remove event handlers. With no arguments, removes all handlers. With just an event name, removes all handlers for that event:
circle.off('click', myHandler); // Remove specific handler
circle.off('click'); // Remove all click handlers
circle.off(); // Remove all handlersemit(event, data?)
Emit an event. The event bubbles up to parent elements by default:
circle.emit('custom-event', { value: 42 });Event Object
Event handlers receive an Event object containing type, data (the payload), source (the originating element), and currentTarget (the element handling the event).
stopPropagation()
Prevent the event from bubbling further up the tree:
circle.on('click', (event) => {
event.stopPropagation();
// Parent group's click handler will NOT fire
});Pointer Events
When elements are rendered to a Context, the context automatically delegates DOM pointer events to the correct elements based on hit testing. A Scene manages the render lifecycle, but the context itself owns interaction.
Following browser DOM behavior, pointer events target the topmost element (highest zIndex) at the cursor position. If overlapping elements exist, only the frontmost one receives the event — lower elements are occluded. The event then bubbles up through the parent hierarchy as usual.
Elements with pointerEvents set to 'none' are transparent to hit testing, allowing events to pass through to the next element below.
Tracked Events
The context tracks click, mouseenter, mouseleave, mousemove, dragstart, drag, and dragend events automatically.
const scene = createScene('.container', {
children: [circle],
});
scene.render();
circle.on('mouseenter', () => {
circle.fill = '#ff006e';
scene.render();
});
circle.on('mouseleave', () => {
circle.fill = '#3a86ff';
scene.render();
});IMPORTANT
Pointer events only work when elements have been rendered to a context. The context handles DOM event listening and hit testing — see Context: Interaction.
Drag Events
Ripl supports drag interactions on elements via dragstart, drag, and dragend events. A drag begins when the pointer is pressed on an element and moved beyond a configurable threshold (default 3px). Once the threshold is exceeded, dragstart fires, followed by drag on each subsequent move, and dragend on pointer release.
circle.on('dragstart', (event) => {
console.log('Drag started at', event.data.x, event.data.y);
});
circle.on('drag', (event) => {
circle.cx += event.data.deltaX;
circle.cy += event.data.deltaY;
scene.render();
});
circle.on('dragend', (event) => {
console.log('Drag ended at', event.data.x, event.data.y);
});The drag and dragend events include startX/startY (where the drag originated) and deltaX/deltaY (movement since the last event). Use deltaX/deltaY for repositioning elements to preserve the offset between the cursor and the element's origin.
The drag threshold can be configured via context options:
const context = createContext('.container', {
dragThreshold: 5, // pixels before dragstart fires
});NOTE
Drag events continue to fire even when the pointer moves outside the element, until the pointer is released.
Event Bubbling
Events bubble up through the element hierarchy, just like the DOM. If a circle inside a group emits a click event, the group will also receive it:
const circle = createCircle({
cx: 100,
cy: 100,
radius: 50,
});
const group = createGroup({ children: [circle] });
// This fires when the circle (or any child) is clicked
group.on('click', (event) => {
console.log('Group received click from:', event.source.type);
});Self Option
Use the self option to only handle events that originated from the element itself (not from children):
group.on('click', (event) => {
console.log('Only fires for direct group clicks');
}, { self: true });Custom Events
You can emit and listen for any custom event name:
circle.on('highlight', (event) => {
circle.fill = event.data.color;
});
circle.emit('highlight', { color: '#ff006e' });The pointerEvents Property
The pointerEvents property on elements controls hit testing behavior. Set it to 'all' (default, responds to fill and stroke), 'fill', 'stroke', or 'none' (click-through).
const overlay = createRect({
pointerEvents: 'none', // Click passes through to elements below
fill: 'rgba(0, 0, 0, 0.3)',
x: 0,
y: 0,
width: 400,
height: 300,
});