MapConductor React SDK Samples

Three.js Object (web only) | MapLibre | MapConductor React SDK

MapConductor React SDK Three.js Object (web only) sample using MapLibre.

This interactive example demonstrates the Three.js Object (web only) feature through the MapConductor abstraction on MapLibre.

Code example

import { useEffect, useMemo } from 'react';
import * as THREE from 'three';
import { createGeoPoint, createMapCameraPosition } from '@mapconductor/js-sdk-core';
import { useMapLoaded } from '@mapconductor/js-sdk-react';
import { MapLibreDesign, MapLibreMapView2D, useMapLibreViewState } from '@mapconductor/react-for-maplibre';


// (1) Create the map


const initialCamera = createMapCameraPosition({
  position: createGeoPoint({ latitude: 35.6812, longitude: 139.7671 }),
  zoom: 12,
});


const mapViewState = useMapLibreViewState({
  mapDesignType: MapLibreDesign.OsmBrightJa,
  cameraPosition: initialCamera,
});


// (2) Prepare the data and interaction state
const position = useMemo(() => createGeoPoint({
  latitude: 35.6812,
  longitude: 139.7671,
}), []);


// (3) Render the map and its overlays


function ThreeMapObject({ mapViewState, position }) {
  const isMapLoaded = useMapLoaded();


  useEffect(() => {
    if (!isMapLoaded) return;
    const holder = mapViewState.getMapViewHolder();
    if (!holder) return;


    const scene = new THREE.Scene();
    const camera = new THREE.OrthographicCamera(0, 1, 1, 0, 0.1, 1000);
    camera.position.z = 200;
    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    const width = holder.mapView.clientWidth;
    const height = holder.mapView.clientHeight;
    renderer.setSize(width, height);
    camera.right = width;
    camera.top = height;
    camera.updateProjectionMatrix();
    renderer.domElement.style.position = 'absolute';
    renderer.domElement.style.inset = '0';
    holder.mapView.appendChild(renderer.domElement);


    const object = new THREE.Mesh(
      new THREE.TorusKnotGeometry(13, 4),
      new THREE.MeshNormalMaterial(),
    );
    scene.add(object);


    let frame = 0;
    const draw = () => {
      const offset = holder.toScreenOffset(position);
      if (offset && !(offset instanceof Promise)) {
        object.position.set(offset.x, height - offset.y, 0);
      }
      object.rotation.y += 0.02;
      renderer.render(scene, camera);
      frame = requestAnimationFrame(draw);
    };
    draw();


    return () => {
      cancelAnimationFrame(frame);
      renderer.domElement.remove();
      renderer.dispose();
    };
  }, [isMapLoaded, mapViewState, position]);


  return null;
}


<MapLibreMapView2D state={mapViewState}>
  <ThreeMapObject mapViewState={mapViewState} position={position} />
</MapLibreMapView2D>

How the code works

Render a transparent Three.js canvas over the map and anchor its 3D object with the provider-independent MapViewHolder.toScreenOffset() projection.

useMapLoaded holds the effect back until the map exists, then getMapViewHolder() returns a holder whose WebGL canvas is appended over the map.

On each frame holder.toScreenOffset(position) converts the geographic coordinate into a pixel offset, so the 3D object stays pinned while the map moves — the same call on every provider.