Skip to content

HTML Script Tag

This guide explains how to use VTK-Wasm directly in an HTML file using a <script> tag without a build step.

The following examples rely on loading the vtk.umd.js bundle from a CDN. Also to mainly focus on the initialization part, we've externalized the JS/WASM code since that part does not change.

Load WASM as module

In this example we pre-load the WASM module and therefore we don't need to provide any URL for loading it when creating the vtk namespace.

html
<html>
  <head>
    <script
      src="/path/to/vtkWebAssembly.mjs"
      type="module"
    ></script>
    <script src="https://unpkg.com/@kitware/vtk-wasm/vtk-umd.js"></script>
    <script src="example.js"></script>
  </head>
  <body>
    <canvas id="vtk-wasm-window"></canvas>
    <script>
      vtkWASM.createNamespace().then(buildWASMScene);
    </script>
  </body>
</html>
js
async function buildWASMScene(vtk, titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({fontSize: 22});
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const canvasSelector = "#vtk-wasm-window";
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({ 
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}

Defer WASM loading

In this example, since we didn't load the WASM module, we need to specify from where it should be loaded.

In this context we provide the URL where the WASM bundle can be found and used from.

html
<html>
  <head>
    <script src="https://unpkg.com/@kitware/vtk-wasm/vtk-umd.js"></script>
    <script src="example.js"></script>
  </head>
  <body>
    <canvas id="vtk-wasm-window" tabindex="-1" onclick="focus()"></canvas>
    <script>
      vtkWASM.createNamespace("https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.5.20251215/vtk-9.5.20251215-wasm32-emscripten.tar.gz")
      .then((vtk) => {
        buildWASMScene(vtk, "This scene passes the VTK.wasm bundle from GitLab registry to createNamespace()");
      });
    </script>
  </body>
</html>
js
async function buildWASMScene(vtk, titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({fontSize: 22});
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const canvasSelector = "#vtk-wasm-window";
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({ 
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}

Full Screen Viewer

Defer WASM loading with annotation

In this example we tag the script to autoload WASM directly from the VTK repository's package registry and create a global vtk namespace. You can customize the wasm architecture and version by changing the data-url.

html
<html>
  <head>
    <script
      src="https://unpkg.com/@kitware/vtk-wasm/vtk-umd.js"
      id="vtk-wasm"
      data-url="https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.5.20250913/vtk-9.5.20250913-wasm32-emscripten.tar.gz"
    ></script>
    <script src="example.js"></script>
  </head>
  <body>
    <canvas id="vtk-wasm-window" tabindex="-1" onclick="focus()"></canvas>
    <script>
      vtkReady.then((vtk) => {
        buildWASMScene(vtk,"This scene points the data-url in script tag to the VTK.wasm bundle from GitLab registry"); // Also available on window.vtk
      });
    </script>
  </body>
</html>
js
async function buildWASMScene(vtk, titleText = "Sample VTK.wasm scene") {

  function createSharedTextProperty() {
    const textProperty = vtk.vtkTextProperty({fontSize: 22});
    return textProperty;
  }

  async function createLookupTable(scalarRange) {
    const lut = vtk.vtkColorTransferFunction();
    await lut.setColorSpaceToHSV();
    const colorSeries = vtk.vtkColorSeries({ colorScheme: 16 });
    const numColors = await colorSeries.getNumberOfColors();
    const scalarDiff = (scalarRange[1] - scalarRange[0]) / numColors;
    for (let i = 0; i < numColors; i++) {
      const color = await colorSeries.getColor(i);
      const t = scalarRange[0] + i * scalarDiff;
      lut.addRGBPoint(
        t,
        color[0] / 255,
        color[1] / 255,
        color[2] / 255,
      );
    }
    await lut.build();
    return lut;
  }

  async function createTitleTextActor(titleText, textProperty) {
    const textActor = vtk.vtkTextActor({ input: titleText, textProperty });
    const position = await textActor.getPositionCoordinate();
    await position.setCoordinateSystemToNormalizedViewport();
    return textActor;
  }

  // Create a VTK source. Output has a point data array named "Scalars" whose range is [0, PI].
  const shapes = vtk.vtkPartitionedDataSetCollectionSource({ numberOfShapes: 2 });
  const lut = await createLookupTable([0.0, Math.PI]);

  const mapper = vtk.vtkCompositePolyDataMapper({ lookupTable: lut });
  await mapper.setInputConnection(await shapes.getOutputPort());
  const actor = vtk.vtkActor({ mapper, scale: [0.1, 0.1, 0.1] });
  actor.property.edgeVisibility = true;
  actor.property.edgeColor = [0.2, 0.2, 0.2];

  const textProperty = createSharedTextProperty();

  // Create an actor that displays the title.
  const titleTextActor = await createTitleTextActor(titleText, textProperty);

  // Setup rendering part
  const renderer = vtk.vtkRenderer({ background: [0.384314, 0.364706, 0.352941] });
  await renderer.addActor(actor);
  await renderer.addActor(titleTextActor);
  await renderer.resetCamera();

  // Create a RenderWindow and bind it to a canvas in the DOM
  const canvasSelector = "#vtk-wasm-window";
  const renderWindow = vtk.vtkRenderWindow({ canvasSelector });
  await renderWindow.addRenderer(renderer);
  const interactor = vtk.vtkRenderWindowInteractor({
    canvasSelector,
    renderWindow,
  });
  await interactor.interactorStyle.setCurrentStyleToTrackballCamera();

  // Create camera widget
  const cameraOrientation = vtk.vtkCameraOrientationWidget({ interactor, parentRenderer: renderer });
  cameraOrientation.enabled = true;

  // Display the scalar bar at the bottom with a horizontal orientation
  const scalarBarActor = vtk.vtkScalarBarActor({ 
    lookupTable: lut,
    title: "Scalars",
    titleTextProperty: textProperty,
    labelTextProperty: textProperty,
    annotationTextProperty: textProperty,
    unconstrainedFontSize: true,
  });
  const scalarBar = vtk.vtkScalarBarWidget({ scalarBarActor, interactor, defaultRenderer: renderer });
  const scalarBarRepresentation = await scalarBar.getRepresentation();
  await scalarBarRepresentation.setOrientation(0); // 1: vertical, 0: horizontal
  const lowerLeftPosition = await scalarBarRepresentation.getPositionCoordinate();
  await lowerLeftPosition.setValue([0.1, 0.05]);
  scalarBar.enabled = true;

  // Trigger render and start interactor
  await interactor.start();
}

Full Screen Viewer

Configuration options

The method createNamespace(url, config) takes two arguments. The first one is used to specify the base directory where the wasm file from VTK will be find. When the module is loaded, the url parameter could be skipped. For the config it is aimed to tune how you would like your WASM environement to behave. The following sections cover the various options and what it means.

  • { rendering: 'webgl', mode: 'sync' }
    • Using WebGL2 for rendering.
    • Using synchronous method execution.
  • { rendering: 'webgl', mode: 'async' }
    • Using WebGL2 for rendering.
    • Using asynchronous method execution.
    • This require WebAssembly JavaScript Promise Integration (JSPI) support in your browser
  • { rendering: 'webgpu' }
    • Using WebGPU for rendering.
    • WebGPU only works with the asynchronous implementation of method execution.
    • This require WebAssembly JavaScript Promise Integration (JSPI) support in your browser

For the annotation usecase you can add data-config="{'rendering': 'webgpu'}" attribute in your HTML to adjust the config setting.