Appearance
3D viewer
A 3D view of the same plan and markers, built with three.js. It's for viewing only: plans are edited in the 2D editor.
Here is the sample hotel in the exploded view. Drag to orbit, right-drag to pan, scroll to zoom, and click a floor to select it.
Loading the live demo…
Installation
three.js is an optional dependency. Install it only if you use the 3D viewer:
bash
npm install threeThe 3D viewer has its own import path, so apps that don't import it never load three.js:
ts
import { FloorPlanViewer3D } from '@jbaysolutions/floorplan-vue/3d'
import '@jbaysolutions/floorplan-vue/style.css' // same stylesheet as the 2D componentstsx
import { FloorplanViewer3D } from '@jbaysolutions/floorplan-react/3d'js
import '@jbaysolutions/floorplan-elements/3d' // defines <floorplan-viewer-3d>To keep three.js out of your main bundle until it's needed, load the 3D view lazily:
ts
import { defineAsyncComponent } from 'vue'
const Plan3D = defineAsyncComponent(() => import('./Plan3D.vue')) // Plan3D.vue imports FloorPlanViewer3Dtsx
import { Suspense, lazy } from 'react'
const FloorplanViewer3D = lazy(() => import('@jbaysolutions/floorplan-react/3d').then((m) => ({ default: m.FloorplanViewer3D })))
<Suspense fallback={null}>
<FloorplanViewer3D plan={plan} markers={markers} mode="stacked" style={{ height: 500 }} />
</Suspense>js
// define <floorplan-viewer-3d> only when it's needed
await import('@jbaysolutions/floorplan-elements/3d')Usage
vue
<div style="height: 600px">
<FloorPlanViewer3D :plan="plan" :markers="markers" theme="light" mode="stacked" @plan-click="onClick" />
</div>tsx
<FloorplanViewer3D plan={plan} markers={markers} theme="light" mode="stacked" onPlanClick={onClick} style={{ height: 600 }} />html
<floorplan-viewer-3d theme="light" mode="stacked" style="height: 600px"></floorplan-viewer-3d>
<script type="module">
import '@jbaysolutions/floorplan-elements/3d'
const viewer3d = document.querySelector('floorplan-viewer-3d')
viewer3d.plan = plan
viewer3d.markers = markers
viewer3d.addEventListener('plan-click', (e) => onClick(e.detail))
</script>plan-click sends the same payload as the 2D viewer ({ x, y, room, floor }, in cm). A marker placed from a 3D click lands in the same spot in 2D, and the other way round.
What is rendered
| Plan element | In 3D |
|---|---|
| Walls | Each floor's height minus the slab, with darker tops. |
| Floors | Stacked at their real heights, with a concrete slab under each upper floor. See Multi-floor buildings. |
| Doors | Openings 2.10 m high with the leaf standing open; sliding doors are glass panels. |
| Windows | Openings from 0.90 m to 2.10 m with glass. |
| Rooms | Floors with their finish: wood, tile, stone, carpet, concrete or grass. |
| Furniture | Low-poly models for every item in the furniture catalogue, and for registered add-on packs (registerPack3D). |
| Pools | Sunk pools cut into the ground with tiled walls, a sloping floor and see-through water; raised pools stand on the floor, optionally with glass sides. See Outdoor areas. |
| Plants | Built leaf by leaf (leafy plant, monstera, palm, olive tree, ornamental grass). Each one is slightly different, and they scale to the size you give them. |
| Markers | The same markers as in 2D, as pins that fade when a wall hides them. |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
plan | FloorPlan | null | — | The plan to show. |
floor (v-model:floor) | string | null | ground floor | The selected floor. |
mode (v-model:mode) | 'floor' | 'stacked' | 'exploded' | 'stacked' | Multi-floor view: one floor, the whole building, or floors pulled apart. |
showFloorSwitcher | boolean | 'auto' | 'auto' | Floor buttons (top right) for multi-floor plans. |
markers | Marker[] | [] | See Markers. |
theme | 'light' | 'dark' | 'blueprint' | 'paper' | 'mist' | 'ink' | 'graphite' | 'light' | Blueprint renders a see-through wireframe; the minimal themes a white (or graphite) model. |
floorColors | 'natural' | 'room' | 'natural' | Realistic finish colours, or each room's colour. The minimal themes always use flat floors. |
showFurniture | boolean | true | Furniture layer. Stairs and elevators are part of the building and always show (FIXTURE_KINDS). |
showRoomLabels | boolean | true | Room name and area labels. |
showControls | boolean | true | 3D / Top, cutaway, auto-rotate and fit buttons. |
showCoordinates | boolean | true | Live cursor coordinates. |
markerSize | number | 34 | Default marker size in px. |
markerVariant | MarkerVariant | 'pin' | Default marker style. |
alwaysShowLabels | boolean | false | Show marker labels without hovering. |
selectedMarkerId | string | number | null | null | Highlights one marker. |
highlightRooms | string[] | [] | Room ids whose floors glow. |
hoverRooms | boolean | true | Glow the floor under the cursor. |
autoRotate | boolean | false | Slowly orbit the model (showroom mode). |
initialView | 'perspective' | 'top' | 'perspective' | Starting camera. |
fitScale | number | 1 | Multiplies the framing distance of fit() and view changes. Use e.g. 1.3 when overlays cover part of the view. |
precision | number | 1 | Decimal places in emitted coordinates. |
cursor | string | 'crosshair' | CSS cursor. |
In React the props have the same names (autoRotate, floorColors="room"). On the <floorplan-viewer-3d> element they are properties with the same names (viewer3d.autoRotate = true); simple values also work as kebab-case attributes (mode="exploded" auto-rotate). Objects and arrays (plan, markers, highlightRooms) must be set as properties.
Events
| Event | Payload | Description |
|---|---|---|
plan-click | { x, y, room, floor, originalEvent } | Click on a floor, wall or object (not fired after a drag). Clicking another floor selects it. |
marker-click | (marker, event) | A marker was clicked. |
room-hover | Room | null | The pointer entered or left a room. |
update:floor / update:mode | string / mode | The user switched floor / view mode. |
WARNING
Markers can't be dragged in 3D. Use the 2D viewer for that.
The same events in React and on the element:
| Vue | React | HTML element (e.detail) |
|---|---|---|
@plan-click | onPlanClick(e) | plan-click: the event object { x, y, room, floor, originalEvent } |
@marker-click | onMarkerClick(marker, originalEvent) | marker-click: { marker, originalEvent } |
@room-hover | onRoomHover(room) | room-hover: { room } |
v-model:floor (update:floor) | floor + onFloorChange(floorId) | floor property + floor-change: { floor } |
v-model:mode (update:mode) | mode + onModeChange(mode) | mode property + mode-change: { mode } |
vue
<FloorPlanViewer3D :plan="plan" v-model:floor="floorId" v-model:mode="mode" @marker-click="(m) => (selected = m.id)" />tsx
<FloorplanViewer3D
plan={plan}
floor={floorId}
onFloorChange={setFloorId}
mode={mode}
onModeChange={setMode}
onMarkerClick={(m) => setSelected(m.id)}
style={{ height: 600 }}
/>js
viewer3d.addEventListener('floor-change', (e) => (floorId = e.detail.floor))
viewer3d.addEventListener('mode-change', (e) => (mode = e.detail.mode))
viewer3d.addEventListener('marker-click', (e) => (selected = e.detail.marker.id))Methods
In Vue, call methods through a template ref. In React, the ref is the <floorplan-viewer-3d> element itself (useRef<FloorplanViewer3DElement>(null)). On the HTML element, call them on the element.
vue
<script setup lang="ts">
import { ref } from 'vue'
import { FloorPlanViewer3D } from '@jbaysolutions/floorplan-vue/3d'
const viewer3d = ref<InstanceType<typeof FloorPlanViewer3D>>()
</script>
<template>
<FloorPlanViewer3D ref="viewer3d" :plan="plan" />
<button @click="viewer3d?.setMode('exploded')">Explode</button>
</template>tsx
const viewer3d = useRef<FloorplanViewer3DElement>(null)
<FloorplanViewer3D ref={viewer3d} plan={plan} style={{ height: 600 }} />
<button onClick={() => viewer3d.current?.setMode('exploded')}>Explode</button>js
const viewer3d = document.querySelector('floorplan-viewer-3d')
viewer3d.setMode('exploded')
const png = viewer3d.snapshot()| Method | Description |
|---|---|
fit() | Frame the whole plan. |
setView('perspective' | 'top') | Animate to the 3D or the straight-down view. |
setFloor(id) / setMode(mode) | Select a floor / switch between floor, building and exploded views. |
setCutaway(on) | Animate a section cut at about 1.1 m through walls and furniture, so you can see into every room. |
focus(x, y, distance = 7) | Fly the camera to a plan point (distance in metres). In React and on the element: focusPoint(x, y, distance?). |
focusMarker(id, distance?) | Fly to a marker. |
focusRoom(id) | Frame a room. |
snapshot() | Render the current view and return a PNG data: URL (labels and markers are not included). |
planToScreen(x, y, floorId?) | Screen position (px, relative to the viewer) of a plan point, plus visible. |
roomAt({ x, y }) | The room containing a plan point. |
Custom markers
Custom marker templates work as in the 2D viewer (see Markers): the marker slot in Vue, renderMarker in React and markerRenderer on the element. In 3D the state is { selected, occluded }, where occluded is true while the marker is behind a wall or slab from the current camera.
Controls
- Mouse: left-drag orbits, right-drag pans, scroll zooms.
- Touch: one finger orbits, two fingers pan and zoom.
Customising 3D colours
3D colours come from THEMES_3D rather than CSS. Change them before the viewer mounts:
ts
import { THEMES_3D } from '@jbaysolutions/floorplan-vue/3d' // or floorplan-react/3d, floorplan-elements/3d
THEMES_3D.light.colors.wall = '#fffaf0' // warm white walls
THEMES_3D.light.colors.cap = '#0f766e' // teal wall topsEvery built-in theme has an entry: light, dark, blueprint, paper, mist, ink and graphite. The minimal themes also use flatFloors (one indoor and one outdoor floor colour) and mono (how add-on pack and pool colours are muted). See Themes for the 2D side.
Performance notes
- The scene is only redrawn when something changes, so an idle 3D view costs almost nothing.
- Changing the plan, theme, floor or mode rebuilds the scene. Building mode draws the floors above the selected one as light wireframes, which keeps tall buildings fast.
- A plan with a few hundred rooms and furniture items runs comfortably on integrated graphics.