CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-d3-zoom

Pan and zoom SVG, HTML or Canvas using mouse or touch input

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

event-handling.mddocs/

Event Handling

Comprehensive event system for handling zoom interactions and responding to zoom state changes.

Capabilities

Event Listener Registration

Registers event listeners for zoom events.

/**
 * Gets or sets event listeners for zoom events
 * @param typenames - Event type names (space-separated)
 * @param listener - Event listener function or null to remove (optional)
 * @returns Current listener or ZoomBehavior for chaining
 */
on(typenames: string, listener?: EventListener | null): ZoomBehavior | EventListener;

type EventListener = (this: Element, event: ZoomEvent, d?: any) => void;

Usage Examples:

import { select } from "d3-selection";
import { zoom } from "d3-zoom";

const svg = select("svg");
const zoomBehavior = zoom()
  .on("start", function(event) {
    console.log("Zoom started");
    this.style.cursor = "grabbing";
  })
  .on("zoom", function(event) {
    const { transform } = event;
    svg.select("g").attr("transform", transform);
  })
  .on("end", function(event) {
    console.log("Zoom ended");
    this.style.cursor = "grab";
  });

svg.call(zoomBehavior);

// Multiple listeners for same event
zoomBehavior
  .on("zoom.update", updateVisualization)
  .on("zoom.debug", logTransform);

// Remove specific listener
zoomBehavior.on("zoom.debug", null);

// Get current listener
const currentListener = zoomBehavior.on("zoom");

ZoomEvent Object

Event object passed to zoom event listeners.

/**
 * Zoom event object containing event details
 */
interface ZoomEvent {
  /** The zoom behavior that dispatched this event */
  readonly target: ZoomBehavior;
  /** Event type: "start", "zoom", or "end" */
  readonly type: "start" | "zoom" | "end";
  /** Current zoom transform */
  readonly transform: ZoomTransform;
  /** Original DOM event that triggered the zoom */
  readonly sourceEvent: Event;
}

Usage Examples:

import { zoom } from "d3-zoom";

const zoomBehavior = zoom()
  .on("zoom", function(event) {
    const { target, type, transform, sourceEvent } = event;
    
    console.log("Event type:", type); // "zoom"
    console.log("Transform:", transform.toString());
    console.log("Source event:", sourceEvent.type); // "wheel", "mousemove", etc.
    console.log("Zoom behavior:", target === zoomBehavior); // true
    
    // Apply transform
    svg.select("g").attr("transform", transform);
  });

Event Types

Different types of zoom events and their timing.

type ZoomEventType = "start" | "zoom" | "end";

Start Events

Fired when a zoom gesture begins.

Triggers:

  • Mouse button press
  • First touch contact
  • First wheel event
  • Programmatic zoom start

Usage Examples:

import { zoom } from "d3-zoom";

const zoomBehavior = zoom()
  .on("start", function(event) {
    // Visual feedback
    this.classList.add("zooming");
    
    // Disable other interactions
    this.style.pointerEvents = "none";
    
    // Log interaction type
    const { sourceEvent } = event;
    if (sourceEvent.type === "mousedown") {
      console.log("Pan started");
    } else if (sourceEvent.type === "wheel") {
      console.log("Zoom started");
    } else if (sourceEvent.type === "touchstart") {
      console.log("Touch interaction started");
    }
  });

Zoom Events

Fired during zoom transform changes.

Triggers:

  • Mouse movement during drag
  • Wheel events
  • Touch movement
  • Programmatic transform changes

Usage Examples:

import { zoom } from "d3-zoom";
import { select } from "d3-selection";

const svg = select("svg");
const zoomBehavior = zoom()
  .on("zoom", function(event) {
    const { transform, sourceEvent } = event;
    
    // Apply transform to content
    svg.select(".content").attr("transform", transform);
    
    // Update scale-dependent elements
    svg.selectAll(".scale-dependent")
      .attr("stroke-width", 1 / transform.k);
    
    // Update info display
    select("#zoom-info")
      .text(`Scale: ${transform.k.toFixed(2)}, X: ${transform.x.toFixed(0)}, Y: ${transform.y.toFixed(0)}`);
    
    // Throttle expensive operations
    if (sourceEvent && sourceEvent.type === "wheel") {
      clearTimeout(this._updateTimeout);
      this._updateTimeout = setTimeout(() => {
        updateExpensiveVisualization(transform);
      }, 100);
    } else {
      updateExpensiveVisualization(transform);
    }
  });

End Events

Fired when a zoom gesture completes.

Triggers:

  • Mouse button release
  • Touch end
  • Wheel idle timeout (150ms)
  • Programmatic zoom completion

Usage Examples:

import { zoom } from "d3-zoom";

const zoomBehavior = zoom()
  .on("end", function(event) {
    // Remove visual feedback
    this.classList.remove("zooming");
    this.style.pointerEvents = "";
    
    // Save zoom state
    const { transform } = event;
    localStorage.setItem("zoomState", JSON.stringify({
      k: transform.k,
      x: transform.x,
      y: transform.y
    }));
    
    // Trigger expensive post-zoom operations
    updateDataLayers(transform);
    recalculateVisibleItems(transform);
  });

Event Context and Data

Understanding the context and data available in event handlers.

Usage Examples:

import { zoom } from "d3-zoom";
import { select, selectAll } from "d3-selection";

// Bind data to elements
const nodes = selectAll(".node")
  .data(nodeData)
  .call(zoom().on("zoom", function(event, d) {
    // 'this' is the DOM element
    // 'event' is the ZoomEvent
    // 'd' is the bound data for this element
    
    console.log("Element:", this);
    console.log("Data:", d);
    console.log("Transform:", event.transform);
    
    // Apply transform with data-specific scaling
    select(this)
      .attr("transform", event.transform)
      .select("circle")
      .attr("r", d.baseRadius / event.transform.k);
  }));

// Global zoom behavior
const globalZoom = zoom()
  .on("zoom", function(event) {
    // 'this' is the element zoom was applied to
    // No bound data ('d' is undefined)
    
    selectAll(".zoomable")
      .attr("transform", event.transform);
  });

select("svg").call(globalZoom);

Advanced Event Patterns

Common patterns for complex zoom event handling.

Debounced Events:

import { zoom } from "d3-zoom";

function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

const zoomBehavior = zoom()
  .on("zoom", function(event) {
    // Immediate updates
    svg.select("g").attr("transform", event.transform);
  })
  .on("zoom.debounced", debounce(function(event) {
    // Expensive operations
    updateComplexVisualization(event.transform);
  }, 250));

State Management:

import { zoom } from "d3-zoom";

let zoomState = {
  isZooming: false,
  startTransform: null,
  currentTransform: null
};

const zoomBehavior = zoom()
  .on("start", function(event) {
    zoomState.isZooming = true;
    zoomState.startTransform = event.transform;
  })
  .on("zoom", function(event) {
    zoomState.currentTransform = event.transform;
    
    // Calculate zoom delta
    const deltaK = event.transform.k / zoomState.startTransform.k;
    const deltaX = event.transform.x - zoomState.startTransform.x;
    const deltaY = event.transform.y - zoomState.startTransform.y;
    
    console.log("Zoom delta:", { deltaK, deltaX, deltaY });
  })
  .on("end", function(event) {
    zoomState.isZooming = false;
    
    // Determine interaction type
    const transform = event.transform;
    const startTransform = zoomState.startTransform;
    
    if (Math.abs(transform.k - startTransform.k) > 0.1) {
      console.log("User zoomed");
    } else if (Math.abs(transform.x - startTransform.x) > 5 || 
               Math.abs(transform.y - startTransform.y) > 5) {
      console.log("User panned");
    }
  });

Event Delegation:

import { zoom } from "d3-zoom";
import { select } from "d3-selection";

// Single zoom behavior for multiple elements
const sharedZoom = zoom()
  .on("zoom", function(event) {
    const targetClass = this.className;
    
    // Different behavior based on element
    if (targetClass.includes("main-view")) {
      // Main visualization
      select(this).select("g").attr("transform", event.transform);
    } else if (targetClass.includes("mini-map")) {
      // Mini-map with inverted behavior
      const invertedTransform = event.transform.scale(0.1);
      select(this).select("g").attr("transform", invertedTransform);
    }
  });

// Apply to multiple elements
select(".main-view").call(sharedZoom);
select(".mini-map").call(sharedZoom);

Install with Tessl CLI

npx tessl i tessl/npm-d3-zoom

docs

configuration.md

event-handling.md

index.md

transform-operations.md

zoom-behavior.md

tile.json