IFC Viewer SDK · v1.18.0
Binde den IFC-Viewer in dein eigenes Produkt ein
Füge denselben Browser-Viewer in dein CDE, deinen digitalen Zwilling oder interne Projekt-Tools ein. Lade IFC-Daten direkt aus deiner App, während jedes Modell im Client geparst wird.
Drei Zeilen bis zum ersten Rendern
const viewer = new IfcViewer("#viewer");
await viewer.add("project.ifc", ifcBytes);
// → rendered, validated, all on the client- 100% clientseitig
- Kein Upload-Backend
- Keine Runtime-Abhängigkeiten
- ~6 KB Wrapper
- TypeScript-Typen
- 10 Sprachen
- IDS-validierte Engine
Einrichtung
Schnellstart
Wähle deinen Stack. Kopieren, einfügen, rendern — ohne Build-Schritt, ohne API-Key.
<div id="viewer" style="height:520px"></div>
<script type="module">
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
const viewer = new IfcViewer("#viewer");
// Load IFC bytes from your app — nothing is uploaded
const bytes = await fetch("/models/project.ifc").then(r => r.arrayBuffer());
await viewer.add("project.ifc", bytes);
</script>Null JavaScript — Tag in eine beliebige Seite oder ein Dashboard setzen und Attribute vergeben.
<script type="module" src="https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js"></script>
<ifc-viewer model="https://your-cde.com/model.ifc" ui="minimal" accent="#3fa866"
style="display:block;height:520px"></ifc-viewer>Im Effect mounten, beim Unmount abbauen. Sicher bei der doppelten Ausführung im StrictMode von React 18.
import { useEffect, useRef } from "react";
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
export function ModelViewer({ url }) {
const host = useRef(null);
useEffect(() => {
const viewer = new IfcViewer(host.current, { model: url });
return () => viewer.dispose(); // StrictMode-safe teardown
}, [url]);
return <div ref={host} style={{ height: 520 }} />;
}In onMounted mounten, in onUnmounted aufräumen.
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/ifc-viewer.es.js";
const host = ref(null);
let viewer;
onMounted(() => { viewer = new IfcViewer(host.value, { model: "https://host/m.ifc" }); });
onUnmounted(() => viewer && viewer.dispose());
</script>
<template>
<div ref="host" style="height: 520px" />
</template>Gehostet oder selbst gehostet
Die Snippets importieren von unserem CDN, das auf jedem Origin funktioniert. Zum Selbsthosten lieferst du ifc-viewer.es.js von deiner eigenen Domain aus — das SDK ermittelt die App-URL relativ zum Skript. Läuft die App woanders? Setze die Option baseUrl.
Noch kein npm-Paket: Modul per URL importieren (oben) oder ifc-viewer.es.js in deine App übernehmen.
Beweis
Live-Demo
Diese Seite führt das SDK aus. Klicke einen Schalter, um ein Beispiel-IFC zu laden und den Viewer zu steuern — jeder Button zeigt den Aufruf, den er macht.
⚠ Der Beispiel-Host drosselt möglicherweise die Anfragen. Versuche es gleich noch einmal.
Architektur
So funktioniert es
Ein winziger Wrapper in deiner Seite, der vollständige Viewer in einem isolierten iframe, deine Bytes dazwischen übertragen — und dort verarbeitet, wo sie ohnehin liegen.
Deine App
Ein ES-Modul von ~6 KB, das ein iframe und eine typisierte JS-API erzeugt. Nichts vom schweren 3D- und Parsing-Gewicht landet in deinem Bundle.
postMessage-Brücke
IFC-Bytes werden als ArrayBuffer an das iframe übertragen (ohne Kopie). Befehle und Abfragen sind korrelierte Request/Response-Nachrichten.
Der Viewer
three.js + web-ifc, zu WebAssembly kompiliert, parsen und rendern das Modell im iframe — auf dem Rechner der Besucherin oder des Besuchers.
Warum es privat ist
Modelle erreichen nie einen Server. Keine Upload-Pipeline abzusichern, keine Frage der Datenresidenz, keine Warteschlange nach Dateigröße.
Warum es skaliert
Die Berechnung läuft auf dem Gerät der jeweiligen Besucher. Ein 200-MB-Modell auszuliefern kostet dasselbe wie ein 2-MB-Modell — es ist eine statische Datei.
Konzepte
Kernkonzepte
Vier Dinge, die man vor dem Bauen wissen sollte.
Mounten & Bereitschaft
new IfcViewer(target) mountet sofort und stellt Befehle in eine Warteschlange, bis der Viewer bereit ist. await IfcViewer.create(target) (oder whenReady()) liefert eine Instanz, die du direkt verwenden kannst.
// queues commands until ready
const viewer = new IfcViewer("#viewer");
// …or await an instance that is ready to use
const viewer = await IfcViewer.create("#viewer");Bytes oder URL
add(name, bytes) überträgt Bytes, die du bereits hast — kein CORS, nichts hochgeladen; der Buffer wird transferiert (detached), gib also eine Kopie mit, wenn du ihn noch brauchst. addFromUrl(url) lädt stattdessen eine öffentliche URL mit CORS.
// you already have the bytes — no CORS, nothing uploaded
await viewer.add("a.ifc", bytes);
// …or load a public, CORS-enabled URL
await viewer.addFromUrl("https://host/b.ifc");Events
Mit on(event, cb) abonnieren, das eine Funktion zum Abbestellen zurückgibt. Verfügbar sind ready, model-progress, model-loaded, validation-completed, model-error und element-selected.
const off = viewer.on("model-loaded", (m) => {
console.log(m.fileName, m.elementCount);
});
// later: off();Abfragen
Daten bei Bedarf abrufen: getModels, getElement, getValidation, getStats, getIssues, checkIds, checkEir, screenshot. Jede ist Request/Response und wird nach einem Timeout abgelehnt (30 s; IDS / EIR bis 120 s).
const models = await viewer.getModels();
const { qualityScore } = await viewer.getValidation();
const png = await viewer.screenshot();Referenz
API-Referenz
Jede Methode, gruppiert — mit Signaturen und Rückgabetypen. Danach folgen Konstruktor-Optionen, Events und die Web Component <ifc-viewer>.
Konstruktor
new IfcViewer(target, options?)IfcViewerIn einen CSS-Selektor oder ein Element einhängen. Siehe die Optionen unten.
IfcViewer.create(target, options?)Promise<IfcViewer>Erzeugt einen Viewer und löst auf, sobald er Befehle annehmen kann.
Modelle laden
add(name, bytes)Promise<ModelLoadedEvent>IFC-Bytes (ArrayBuffer/Uint8Array) aus deiner App laden. Der Buffer wird für eine kopierfreie Übergabe transferiert.
addFromUrl(url, name?)Promise<ModelLoadedEvent>Eine öffentliche, CORS-fähige IFC-URL laden.
removeModel(modelId)voidEntlädt ein Modell per id.
clear()voidAlle geladenen Modelle entfernen.
Kamera & Auswahl
select(expressId | globalId, modelId?)voidEin Element auswählen und einpassen — per IFC-expressID oder per GlobalId (die auch sein Modell findet).
setView(view)voidZu einer benannten Ansicht der Szene fliegen — iso, front, back, left, right, top, bottom — mit der ganzen Szene im Bild. Ohne Antwort (von 1.14 bis 1.17 ignorierte der Viewer sie).
frame({ view?, fill?, azimuth?, elevation?, animate? })Promise<{ scope }>Bildausschnitt für die Präsentation: Das Modell füllt `fill` des Bildes (0,2–0,98, Standard 0,85), an seine Ecken angepasst statt an die Hüllkugel. azimuth / elevation wählen jeden Blickwinkel.
frame(elementId, modelId?, options?)Promise<{ scope: 'element' }>Ein Element (expressID) einpassen: aus dem aktuellen Blickwinkel, sofern keine Ansicht angegeben ist. Lehnt ab, wenn es nicht im Modell ist.
fit() · reset()voidAktives Modell aus dem aktuellen Blickwinkel mit Rand einpassen / Kamera zurücksetzen.
setCamera(position, direction)voidPositioniert die Kamera an einer Position mit Blickrichtung.
getCamera()Promise<CameraState | null>Wo die Kamera steht und wohin sie blickt — speichern und mit lookAt wiederherstellen.
lookAt(position, target, animate?)Promise<void>Kamera an eine Position fliegen, Blick auf ein Ziel (Szenenmeter, Y oben).
setWalkMode(enabled, { speed? })Promise<WalkState>Begehungsmodus in der Ich-Perspektive (WASD, Ziehen zum Umsehen, Esc zum Verlassen). Löst walk-changed aus.
getWalkState()Promise<WalkState>Ob der Begehungsmodus aktiv ist und mit welcher Geschwindigkeit.
Sichtbarkeit
isolate(ifcType?)voidEine Kategorie isolieren (z. B. "IfcWall"); ohne Wert zum Zurücksetzen.
showAll()voidVolle Sichtbarkeit wiederherstellen (Ausgeblendetes + Isolation aufheben).
hideElements(ids, modelId?) · showElements(ids, modelId?)voidBlendet eine Menge von Elementen per expressID aus / ein.
setModelVisible(modelId, visible)Promise<void>Ein Modell einer föderierten Szene ein- oder ausblenden, ohne es zu entladen.
setModelOpacity(opacity, modelId?)Promise<void>Ein Modell transparent machen (0,05–1) — z. B. die Architektur um die TGA.
isolateModel(modelId | null)Promise<void>Nur dieses Modell zeigen; null zeigt wieder alle.
Aussehen & Präsentation
setBackground(background)Promise<BackgroundState>Szenenhintergrund ändern: ein Preset ('white', 'paper', 'blueprint', 'sky', 'studio'), '#rrggbb', '#oben,#unten' oder { top, bottom }. Wird nicht als Präferenz des Besuchers gespeichert.
getBackground()Promise<BackgroundState>Der aktuelle Szenenhintergrund.
setAccent(color)Promise<void>Akzentfarbe der Oberfläche zur Laufzeit ändern (#rrggbb).
setClientMode(enabled)Promise<void>Kundenansicht ein- oder ausschalten — die Ansicht für Stakeholder, wie ui: 'client'.
setRenderQuality(quality)Promise<void>'quality' schaltet das aufwendigere Rendering für ein Titelbild ein; 'standard' für den Alltag.
Sonnenstudie
setSolar(opts?)Promise<SolarState>Echte Schatten zu einem ortsüblichen Datum und einer Uhrzeit: { date: '06-21', time: '18:00' }. Der Standort kommt aus der IFC-Georeferenz; für ein Modell ohne sie location übergeben — nie stillschweigend eine Standardstadt.
getSolar()Promise<SolarState>Datum, Uhrzeit und Zeitzone der Studie und wo der Standort liegt.
Kartenmodus
setSiteContext(opts?)Promise<SiteContextState>Modell mit Gelände und OpenStreetMap-Umgebung auf die Karte setzen. Der Aufruf erklärt die Kachel-Einwilligung Ihrer Seite für ihre Besucher; zeigen Sie die Quellenangaben.
getSiteContext()Promise<SiteContextState>Zustand des Kartenmodus, Platzierung und die anzuzeigenden Quellenangaben.
Szenen
openScene(url | SceneDocument)Promise<void>Eine andere Szene öffnen: die URL einer .scene.json oder ein Szenendokument (es reist gepackt in der Adresse des Frames und muss in einen Link passen). Der Viewer lädt damit neu; wartende Aufrufe werden abgelehnt, ready feuert erneut.
exportScene({ title?, description?, camera? })Promise<SceneExport>Die Szene auf dem Bildschirm als Szenendokument – Modelle per URL, Ebenen, Zwilling, Karte, Kamera – mit den zu nennenden Quellen und einem Link, der sie trägt.
packScene(doc)Promise<string | null>Ein Szenendokument selbst für einen #scene=-Link packen (null, wenn es für einen Link zu groß ist).
Datenebenen und Zwilling
getLayerPresets()Promise<LayerPreset[]>Der Katalog öffentlicher Live-Quellen, in der Sprache des Viewers benannt, mit dem, was jede im Browser braucht (Schlüssel, Proxy), und ob sie Daten nahe dem Standort hat.
addLayer({ preset } | { url, live? } | { geojson })Promise<DataLayerInfo>Eine Ebene hinzufügen: eine Katalogquelle ({ preset }), GeoJSON oder einen Live-Feed unter einer URL ({ url, live }) oder vorhandenes GeoJSON ({ geojson }). Nur für die Sitzung: nie in den Ebenen des Besuchers gespeichert.
getLayers()Promise<DataLayerInfo[]>Alle Datenebenen: Status, Objektanzahl, Ausdehnung, Aktualisierung und Quellenangabe.
setLayerVisible(id, visible)Promise<DataLayerInfo>Eine Ebene ein- oder ausblenden.
frameLayer(id)Promise<void>Die Kamera zu den Objekten einer Ebene führen.
removeLayer(id)Promise<void>Eine Ebene entfernen.
getTwin()Promise<TwinState>Der Betriebszwilling jetzt: Gerätequellen (Status, letzte Messung) und je Bindung die greifende Regel, ihre Farbe, der Beschriftungswert und ob sie alarmiert.
Schnitte
addSection({ axis?, offset?, level?, flip? })Promise<SectionsState & { id }>Schnittebene hinzufügen. { level: 'EG' } ist ein Grundriss auf diesem Geschoss; { axis: 'x', offset: 4.5 } ein Schnitt bei 4,5 m.
updateSection(id, { offset?, enabled?, flipped? })Promise<SectionsState>Ebene verschieben, ein-/ausschalten oder umkehren.
removeSection(id?)Promise<SectionsState>Eine Ebene entfernen, oder alle Schnitte ohne id.
setSectionBox('model' | 'selection' | false)Promise<SectionsState>Schnittbox um das Modell oder die Auswahl; false entfernt sie.
getSections()Promise<SectionsState>Alle Ebenen, die Box und die Geschosse für Ebenenschnitte.
Touren
startTour(template?, { autoplay?, title? })Promise<TourState>Eine vordefinierte Tour starten: 'social', 'client-walkthrough' oder 'technical-review' (geht die Validierungsbefunde durch). Abgespielt von der Tour-Leiste des Viewers.
playTour({ title?, steps }, { startAt?, autoplay? })Promise<TourState>Eine eigene Tour abspielen: Kamerastopps mit Text, Hervorhebungen und isolierten Klassen. autoplay: true oder ms pro Stopp.
nextTourStep() · prevTourStep() · goToTourStep(i)Promise<TourState>Zwischen Stopps wechseln.
setTourAutoplay(autoplay)Promise<TourState>Automatisches Weiterschalten der laufenden Tour ein- oder ausschalten.
stopTour()Promise<TourState>Tour beenden und die Kamera zurückgeben.
getTour()Promise<TourState>Die Tour und ihre Stopps in der Form von playTour() — speichern und später erneut abspielen.
Präsentations-Regie
getPresentationRecipes()Promise<PresentationRecipe[]>Die eingebauten Regie-Rezepte (Meeting-Demo, Reel, LinkedIn-Teaser, Launch, Editorial-Looks…).
createPresentation(recipe?, options?)Promise<PresentationState>Das Modell per Rezept in ein geschnittenes Video verwandeln: Einstellungen aus dem IFC geplant, Texte, Musik. Öffnet Clip Studio mit dem Ergebnis. Fortschritt über presentation-progress.
exportPresentation({ resolution?, music? })Promise<PresentationVideo>Die Präsentation im Browser des Besuchers kodieren und die MP4-Bytes erhalten — hochladen, anhängen oder in einem <video> abspielen.
closePresentation()Promise<void>Clip Studio schließen.
Cover-Studio
getCoverOptions()Promise<CoverCatalog>Die Rezepte, Vorlagen, Formate und Paletten des Cover-Studios.
createCover({ recipe?, template?, format?, palette?, text? })Promise<CoverState>Ein Cover erstellen: ein Rezept ausführen (es nimmt die nötigen Ansichten auf), dann Vorlage, Format, Palette und Texte setzen. Öffnet das Cover-Studio, weiter bearbeitbar.
getCover()Promise<CoverState | null>Das Cover im aktuellen Stand, oder null, wenn das Studio geschlossen ist.
exportCover({ type?, slide? })Promise<CoverFile>Exportieren: png / jpeg (eine Seite), pdf / pptx (das ganze Dokument), zip (alle Seiten). Liefert die Bytes.
closeCover()Promise<void>Das Cover-Studio schließen.
Artikel-Kit
activate()voidEinen verzögerten Viewer jetzt starten (was der Poster-Button tut). Frühere Aufrufe warten in der Warteschlange.
bindSteps(steps, { rootMargin? })() => voidScrollytelling: Während jeder Absatz die Bildschirmmitte kreuzt, wird eingepasst, isoliert, die Sonne bewegt oder Ihr Code ausgeführt. Liefert eine Funktion zum Lösen.
setTurntable(enabled | degPerSec)Promise<{ active, speed }>Langsame Drehung im Leerlauf (Grad pro Sekunde; false stoppt). Stoppt bei der ersten Berührung, nie bei reduzierter Bewegung.
toggleFullscreen()Promise<void>Die Abbildung auf Vollbild vergrößern oder zurück.
setPaused(paused)Promise<{ paused }>Das Rendern anhalten oder fortsetzen. pauseOffscreen erledigt das für Sie.
Szenengruppen
getGroups()Promise<SceneGroupsState>Alle Gruppen der Szene — aus dem IFC abgeleitet (Projekt, Grundstück, Standort) und von Nutzern angelegt.
createGroup(name, modelIds?)Promise<string>Eine Gruppe anlegen, optional mit Modellen. Liefert ihre id.
renameGroup(id, name) · deleteGroup(id)Promise<void>Eine Nutzergruppe umbenennen oder löschen. Automatische Gruppen sind nicht editierbar.
assignToGroup(itemId, groupId | null | 'loose')Promise<void>Ein Modell oder eine Punktwolke in eine Gruppe verschieben; null gibt es der automatischen Gruppierung zurück, 'loose' lässt es ohne Gruppe.
setGroupVisible(id, visible) · isolateGroup(id | null)Promise<void>Eine ganze Gruppe ein- oder ausblenden, oder nur eine zeigen.
frameGroup(id)Promise<void>Die Kamera auf eine Gruppe ausrichten — ausgeblendete Mitglieder eingeschlossen.
Messungen
setMeasureTool(tool)Promise<void>Messwerkzeug für den Besucher aktivieren (öffnet das Mess-Panel) oder 'none'. Ergebnisse kommen über measurements-changed.
getMeasurements()Promise<MeasurementsState>Alle Messungen auf dem Bildschirm. Werte in SI — Meter, m², Grad — unabhängig von der Anzeige.
clearMeasurements(id?)Promise<MeasurementsState>Eine Messung entfernen, oder alle.
Abfragen
getModels()Promise<ModelSummary[]>Listet die geladenen Modelle. Gibt ein Promise zurück.
findElements({ ifcClass?, globalId?, name?, modelId?, limit? })Promise<ElementSummary[]>Elemente nach IFC-Klasse, GlobalId und/oder Name in den geladenen Modellen finden. Jeder Treffer enthält, was getElement(), select() und frame() erwarten.
getElement(expressId | globalId, modelId?)Promise<IfcElementData | null>Per expressID oder GlobalId. Die IFC-Daten eines Elements: Attribute mit GlobalId, seine Eigenschafts- und Mengensätze und die seines Typs — typeName, typeProperties — mit Einheiten. effectivePropertySets führt beide zusammen, der eigene Wert des Elements gewinnt.
validate(modelId?, { force? })Promise<ValidationRunResult>Ein Modell jetzt validieren (standardmäßig das aktive) und mit Health Score und Anzahl der Befunde auflösen. Lehnt mit dem Grund ab.
getValidation()Promise<ValidationSummary | null>Das angezeigte Prüfergebnis, oder null, bevor ein Lauf eines erzeugt hat. Siehe „Ablauf der Validierung“.
getValidationStatus()Promise<ValidationStatus>Wo die Validierung gerade steht: idle, running (mit Fortschritt), done oder error.
getStats()Promise<StatsResult>Elementanzahl je Kategorie und Modell — für Dashboard-Diagramme. Gibt ein Promise zurück.
getIssues({ severity?, limit?, modelId? })Promise<IssuesResult>Validierungsmeldungen für eine Tabelle (Filter nach Schweregrad / Limit). Gibt ein Promise zurück.
screenshot()Promise<string>Erfasst die aktuelle 3D-Ansicht als PNG-Data-URL. Gibt ein Promise zurück.
compare({ base, head, baseLabel?, headLabel? })Promise<{ changes }>Vergleicht zwei Lieferungen per URL (über die GlobalId) und öffnet die Vergleichsansicht mit dem Ergebnis.
Panels
openPanel(panel | null) · closePanel()voidÖffnet ein Werkzeug-Panel, oder null, um das geöffnete zu schließen. Ein nicht verfügbares Panel — vom Chrome ausgeblendet oder ohne geladene Daten — bleibt wirkungslos und ist kein Fehler.
getPanels()Promise<PanelsResult>Welches Panel offen ist und welche gerade angeboten werden. Ein Werkzeug, das in `available` fehlt, lässt sich nicht öffnen; es ist nicht bloß deaktiviert.
setPanels(panels)voidBeschränkt die Leiste zur Laufzeit auf diese Panels. Gleiches Vokabular wie der URL-Parameter `panels=` und hat Vorrang davor. Schränkt das Angebot ein und erweitert es nie; ein leeres Array bedeutet keine Leiste.
Punktwolken
addPointCloud(fileName, bytes)Promise<string>Lädt einen Scan aus Bytes — LAS, LAZ, COPC, PLY, PCD oder Text. Der Puffer wird übertragen, nicht kopiert. Liefert die Cloud-ID.
addPointCloudFromUrl(url, fileName?)Promise<string>Lädt einen Scan, den der Viewer selbst abruft. Die URL muss CORS erlauben.
listPointClouds()Promise<PointCloudInfo[]>Alle geladenen Scans. `pointCount` ist das Residente, `declaredCount` das, was die Datei enthält — sie unterscheiden sich, wenn das Budget den Parse abgeschnitten hat.
removePointCloud(cloudId) · clearPointClouds()Promise<void>Entlädt einen Scan und gibt seine GPU-Puffer frei, oder entlädt alle.
setPointCloudVisible(cloudId, visible)Promise<void>Blendet einen Scan ein oder aus, ohne ihn zu entladen.
fitPointCloud(cloudId?)Promise<void>Rahmt die Kamera auf einen Scan, oder auf den zuerst geladenen.
setPointCloudDisplay(display, renderBudget?)Promise<void>Punktgröße, Deckkraft, Farbmodus, Dichte, Konfidenzschwelle — für alle Scans gemeinsam. Jedes ist ein Shader-Uniform und damit auch bei 20 Millionen Punkten sofort wirksam.
inspectPointCloud(enabled?)Promise<void>Aktiviert Klick-zum-Auslesen. Ein Klick auf einen Punkt sendet dann `pointcloud-picked` mit den Koordinaten AUS DER DATEI neben denen der Szene.
setPointCloudPlacement(placement, cloudId?)Promise<void>Verschiebt einen Scan von Hand: Position, Gierung, Nivellierung, Maßstab. Partiell und begrenzt; wirkt auf die ermittelte Ausrichtung, ohne sie zu ersetzen.
setPointCloudUpAxis(axis, cloudId?)Promise<void>Korrigiert, welche Achse der Scan als oben behandelt. PLY, PCD und Text geben keine Orientierung an, der Viewer leitet sie ab — prüfen Sie `upAxisSource`.
Imported 3D models
addMesh(files)Promise<string>Importiert ein Modell aus Bytes. Erwartet eine LISTE: eine .gltf braucht ihre .bin und Texturen, eine .obj ihre .mtl — allein ergibt die Eingabedatei graue Geometrie.
addMeshFromUrl(urls)Promise<string>Importiert ein Modell, das der Viewer abruft. Übergeben Sie alle nötigen URLs; sie werden parallel geladen und müssen alle CORS erlauben.
listMeshes()Promise<MeshInfo[]>Alle importierten Modelle mit Dreiecks- und Texturzahlen. Lesen Sie `unitSource` und `upAxisSource`, bevor Sie den Werten daneben trauen.
removeMesh(meshId?) · clearMeshes()Promise<void>Entfernt einen Import und gibt Geometrie, Materialien und Texturen frei, oder entfernt alle.
setMeshVisible(visible, meshId?)Promise<void>Blendet einen Import ein oder aus, ohne ihn zu entladen.
fitMesh(meshId?)Promise<void>Rahmt die Kamera auf einen Import, oder auf alle.
setMeshPlacement(placement, meshId?)Promise<void>Platziert einen Import von Hand: Position, Gierung, Nivellierung, Maßstab. Partiell und begrenzt. Importe starten mittig über dem IFC, auf dessen Boden.
setMeshUpAxis(axis, meshId?)Promise<void>Korrigiert die Hochachse der Quelle. Nur bei OBJ sinnvoll — die glTF-Spezifikation schreibt Y oben vor, daher meldet sie `upAxisSource: 'declared'`.
setMeshUnit(unitScale, meshId?)Promise<void>Korrigiert die Quelleinheit: 1 Meter, 0,01 Zentimeter, 0,001 Millimeter, 0,3048 Fuß. Keines dieser Formate hält sie fest, der Viewer leitet sie aus der Modellgröße ab.
IDS- und EIR-Konformität
checkIds(idsXml)Promise<IdsResult>Prüft das Modell gegen ein buildingSMART-IDS (.ids-XML). Läuft in einem Worker. Liefert bestanden/nicht bestanden je Spezifikation.
checkEir(profile)Promise<IdsResult>Prüft das Modell gegen ein EIR-/BIM-Validierungsprofil — ein Objekt, sein JSON (auch die Kurzform) oder die Id eines eingebauten Profils wie 'builtin-en14351-1'. Wird zu IDS kompiliert und läuft auf derselben Engine: dieselbe IdsResult-Form wie checkIds.
Sprachen
setLanguage(lang)voidDie UI-Sprache zur Laufzeit ändern.
getLanguages()string[]Unterstützte Sprachcodes auflisten (Labels via IfcViewer.LANGUAGES).
Lebenszyklus & Events
whenReady()Promise<void>Löst auf, sobald der Viewer Befehle annehmen kann.
isReadybooleanGetter — true, sobald der Viewer seine Bereitschaft gemeldet hat.
on(event, cb)() => voidEin Event abonnieren; gibt eine Abmelde-Funktion zurück.
off(event, cb)voidEntfernt einen zuvor hinzugefügten Event-Listener.
dispose()voidDen Viewer abbauen und das iframe entfernen.
Gleichzeitige Aufrufe von add() / addFromUrl() werden intern serialisiert, und jedes Promise ist seinem eigenen Ladevorgang zugeordnet — ein von der App ausgelöster Ladevorgang (URL-Parameter, Upload im iframe) löst niemals dein add()-Promise auf.
Konstruktor-Optionen
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
ui | 'minimal' | 'full' | 'kiosk' | 'client' | 'article' | 'embed' | 'minimal' | UI-Preset. 'embed': für eine Host-App, die die Datei liefert — 3D, Eigenschaften bei Auswahl, eine kompakte Leiste mit Prüfen und Messen; ohne Öffnen, Teilen, Konto oder Sprache. |
toolbar | boolean | preset | Die Werkzeugleiste zeigen (Standard: die des Presets). false lässt 3D-Ansicht und Panels. |
tools | ('validate' | 'measure')[] | ['validate', 'measure'] | Werkzeuge der kompakten Leiste von ui: 'embed' — 'validate', 'measure'. [] für keine. |
autoFrame | boolean | true | Nach jedem abgeschlossenen Laden die ganze Szene iso mit Rand einpassen. false überlässt Ihnen die Kamera. |
validate | boolean | true | Validierung beim Laden ausführen (speist den Health Score). |
panel | boolean | false | Validierungspanel automatisch öffnen. |
panels | PanelName[] | — | Beschränkt die Werkzeugleiste ab dem ersten Frame auf diese Panels — `['scene','map']`, oder `[]` für keine Leiste. Gleiche Namen wie setPanels und der URL-Parameter `panels=`. Besser als ein setPanels nach dem Laden, das erst die volle Leiste zeigt und dann Werkzeuge entfernt. |
lang | string | auto | UI-Sprache erzwingen (z. B. en, es, ja). |
accent | #rrggbb | brand | Den Viewer farblich an dein Dashboard angleichen. |
model | string | — | Diese öffentliche IFC-URL laden, sobald der Viewer bereit ist. |
background | BackgroundSpec | — | Szenenhintergrund ab dem ersten Frame — ein Preset, '#rrggbb' oder '#oben,#unten'. |
map | boolean | ('terrain' | 'buildings' | 'showcase')[] | — | Modell nach dem Laden auf die Karte setzen: true oder Ebenen ['terrain','buildings','showcase']. |
solar · moon | 'MM-DDTHH:MM' · boolean | — | Sonnenstudie zu einer Ortszeit öffnen: '06-21T18:00'. |
scans | string[] | — | Punktwolken-URLs, die mit dem Modell geladen werden. |
layers | string | — | Zu öffnende Datenebenen-Einrichtung: URL eines aus dem Datenebenen-Panel exportierten JSON (Live-Quellen, Stile, Alarme). Seit 1.16. |
scene | string | — | Zu öffnende Szene: URL eines Szenendokuments (Modelle, Datenebenen, Live-Geräte, Karte, Kamera). Seit 1.17. |
view · fill | CameraView · number | — | Sobald alle Modelle geladen sind, aus dieser Ansicht eng einpassen. ui: 'article' setzt 'iso'. |
wheel | 'always' | 'ctrl' | 'always' | 'ctrl': Das Mausrad scrollt Ihre Seite und zoomt nur mit gedrückter Strg/⌘ — wie eine eingebettete Karte. ui: 'article' setzt es. |
lazy | boolean | 'visible' | false | Nichts laden, bis es gebraucht wird: true zeigt ein Poster mit Button, 'visible' startet, wenn die Abbildung in Sichtweite kommt. |
poster · posterTitle · posterText · launchLabel | string | — | Posterbild, Titel, Text und Button-Beschriftung, bis der Viewer bereit ist. |
aspectRatio | string ('16/10') | — | Größe per Seitenverhältnis ('16/10'): Poster und Viewer teilen die Box, nichts verschiebt die Seite. |
turntable | boolean | number | false | Langsame Drehung nach dem Laden (true = 6°/s, oder Grad pro Sekunde). |
pauseOffscreen | boolean | ui === 'article' | Außerhalb des Bildschirms nicht rendern. Standard true mit ui: 'article'. |
fullscreenButton | boolean | false | Ein kleiner Vergrößern-Button in der Ecke. |
height · width | number | string | '100%' | iframe-Größe (Zahl → px). |
baseUrl | string | auto | Basis-URL der App. Wird aus der Skript-URL abgeleitet. |
loadTimeout | number | 120000 | add()/addFromUrl() nach N ms ablehnen (0 deaktiviert das). |
className · title | string | — | Zusätzliche Klasse / iframe-title. |
on* callbacks | function | — | Bequeme Callbacks — dasselbe wie .on(...). |
Events
| Event | Payload | Beschreibung |
|---|---|---|
ready | { languages } | Viewer eingehängt und bereit. |
model-progress | { percent, phase } | Ladefortschritt (Download → Parsen → Rendern). |
model-loaded | { modelId, fileName, elementCount, fromCache } | Ein Modell wurde geladen. |
validation-started | { modelId } | Eine Validierung hat an einem Modell begonnen — nach dem Laden (validate: true) oder per validate(). |
validation-completed | { modelId, qualityScore, errors, warnings, info, total } | Validierung fertig: Health Score und Anzahlen, und das Modell, auf das sie sich beziehen (null für das föderierte Gesamtergebnis). |
validation-failed | { modelId, message } | Eine Validierung konnte nicht laufen oder wurde nicht fertig — mit dem Grund. |
model-error | { message, url?, name? } | Ein Laden ist fehlgeschlagen. |
element-selected | { expressId, modelId, ifcType, name, globalId } | Der Nutzer hat ein Element gewählt — mit GlobalId, passend zu Ihren eigenen Datensätzen. |
pointcloud-picked | { cloudId, position, sourcePosition, classification, intensity, distance } | Ein Punkt wurde aus einem Scan gelesen — in den Koordinaten der Datei, nicht der Szene. |
map-feature-picked | { id, name?, label?, featureKind, heightM?, heightEstimated } | Ein Gebäude der OpenStreetMap-Umgebung wurde angeklickt. Kontext, kein Modell. |
walk-changed | { active, speed } | Begehungsmodus ein- oder ausgeschaltet — vom Besucher oder vom Host. |
measurements-changed | { tool, units, items } | Eine Messung wurde hinzugefügt, entfernt oder umbenannt. Enthält die ganze Liste. |
tour-started | { title, total, template } | Eine Tour hat begonnen. |
tour-step | { index, total, caption } | Die Tour ist zu einem anderen Stopp gewechselt. |
tour-ended | { completed } | Die Tour wurde beendet; completed, wenn der letzte Stopp erreicht war. |
presentation-progress | { stage, label?, progress } | Die Regie erzeugt oder exportiert eine Präsentation. |
layer-feature-picked | { layerId, layer, featureId, geometry, lonLat, properties } | Ein Objekt einer Datenebene wurde angeklickt: Ebene, Eigenschaften und ein Punkt zum Markieren. |
alert | { kind, from, id, name, rule, count, sample } | Eine Regel einer Datenebene oder des Zwillings hat zu alarmieren begonnen oder aufgehört. |
Statische Member
IfcViewer.LANGUAGES ({ code, label }[], native Namen) und IfcViewer.SUPPORTED_LANGUAGES (Codes) — für eine Sprachauswahl, bevor der Viewer bereit ist.
Web Component
Null JavaScript — Tag in eine beliebige Seite oder ein Dashboard setzen und Attribute vergeben.
Attribute: modeluilangaccentvalidatepanelbase-urltoolbartoolsauto-frame
Events werden als DOM-CustomEvents mit dem Namen ifcviewer:<type> erneut ausgelöst (detail = Payload). Der zugrunde liegende IfcViewer liegt auf .viewer des Elements; getStats / getIssues / screenshot werden durchgereicht.
const el = document.querySelector("ifc-viewer");
el.addEventListener("ifcviewer:validation-completed", (e) => {
updateScore(e.detail.qualityScore);
});
const stats = await el.getStats();Anleitungen
Rezepte
Copy-Paste-Muster für das, was Teams tatsächlich bauen.
Element-Inspektor
Im 3D klicken und die IFC-Property-Sets in dein eigenes Panel lesen.
viewer.on("element-selected", async (e) => {
const data = await viewer.getElement(e.expressId, e.modelId);
renderPanel(data.name, data.globalId, data.propertySets);
});Health-Score-Widget
Qualitätswert und Anzahl der Befunde des Modells in deinem Dashboard zeigen.
viewer.on("validation-completed", (v) => {
setScore(v.qualityScore); // 0–100, or null
setCounts(v.errors, v.warnings, v.info);
});IDS-Konformitätsprüfung
Gegen ein buildingSMART-IDS validieren und pro Spezifikation bestanden / nicht bestanden auflisten.
const ids = await fetch("/specs/project.ids").then(r => r.text());
const res = await viewer.checkIds(ids); // runs in a worker
res.specs.forEach((spec) => {
row(spec.name, spec.status, spec.passedCount + "/" + spec.applicableCount);
spec.failures.forEach((f) => onClick(() => viewer.select(f.expressId, f.modelId)));
});EIR- / BIM-Validierung
Ein editierbares EIR-Profil ausführen (im Stil von ISO 19650) — ohne .ids-Datei. Fehlschläge tragen die GlobalId des Elements.
// An editable EIR profile — object, JSON, or the compact shorthand below.
const profile = {
name: "Door QA",
rules: [
{ type: "requiredProperty", entity: "IfcDoor", property: "FireRating", severity: "error" },
{ type: "allowedValues", entity: "IfcDoor", property: "FireRating",
values: ["EI30", "EI60", "EI90"], severity: "warning" },
],
};
const res = await viewer.checkEir(profile); // same IdsResult as checkIds
res.specs.forEach((spec) => {
spec.failures.forEach((f) => row(spec.name, f.globalId, f.reasons.join(" · ")));
});Eine Szene für einen Blogartikel
Weißer Hintergrund, das Modell an seinem Standort und der Abendschatten der Sommersonnenwende — vorab deklariert, ohne Klicks.
// Declared up front: the iframe boots with all of it, no clicks, no flash.
const viewer = await IfcViewer.create("#viewer", {
model: "https://example.com/house.ifc",
ui: "client",
background: "white",
map: ["terrain", "buildings"], // your page declares tile consent
solar: "06-21T19:30", // summer solstice, site-local
});
// …or change the story as the reader scrolls
await viewer.setSolar({ date: "12-21", time: "09:30" });
const { attributions } = await viewer.getSiteContext(); // show theseGrundriss und Messungen
Einen Grundriss auf einem benannten Geschoss schneiden, den Leser messen lassen und die Ergebnisse in SI-Einheiten auslesen.
const { levels } = await viewer.getSections();
await viewer.addSection({ level: levels[0].name }); // floor plan, 1.2 m up
viewer.setView("top");
await viewer.setMeasureTool("distance");
viewer.on("measurements-changed", ({ items }) => {
// value is SI — metres, m², degrees — whatever the viewer displays
render(items.map((m) => m.kind + ": " + m.value?.toFixed(2)));
});Eine geführte Tour im Artikel
Die Stopps einmal mit getCamera() festlegen und die Tour neben dem Text selbst laufen lassen.
// 1. Once, while writing the post: frame each view and copy the camera.
console.log(JSON.stringify(await viewer.getCamera()));
// 2. In the page: play the stops, self-running, next to your text.
await viewer.playTour({
title: "Casa Poblenou",
steps: [
{ position: { x: 52, y: 29, z: 24 }, target: { x: 18, y: 7, z: -11 }, caption: "The site" },
{ position: { x: 18, y: 40, z: 11 }, target: { x: 18, y: 0, z: 10 },
caption: "Structure", isolate: ["IfcColumn", "IfcSlab"] },
],
}, { autoplay: 6000 });
viewer.on("tour-step", ({ index, caption }) => highlightParagraph(index));Ein Video des Modells, von Ihrer Seite
Eine Präsentation per Rezept erzeugen und das MP4 zurückbekommen — ohne Bildschirmaufnahme, ohne Upload.
viewer.on("presentation-progress", ({ stage, progress }) => bar.value = progress ?? 0);
await viewer.createPresentation("linkedin-teaser", { title: "Casa Poblenou", targetSec: 20 });
const { bytes, mimeType } = await viewer.exportPresentation({ resolution: 1080 });
video.src = URL.createObjectURL(new Blob([bytes], { type: mimeType }));Ein Cover für Ihren Beitrag
Einen Pinterest-Pin oder eine LinkedIn-Karte des Modells mit Ihrem Titel erzeugen und das PNG zurückbekommen.
await viewer.createCover({
recipe: "pinterest",
text: { title: "Casa Poblenou", location: "Barcelona", website: "https://example.com" },
});
const { bytes, mimeType } = await viewer.exportCover({ type: "png" });
img.src = URL.createObjectURL(new Blob([bytes], { type: mimeType }));Ein Modell in einem Artikel
ui: 'article' ist nur die Leinwand, so eingepasst, dass das Modell sie füllt, mit einem Mausrad, das Lesende weiterscrollen lässt. Den Winkel wählt frame().
const viewer = new IfcViewer("#figure", {
ui: "article", // canvas only, tight iso framing, wheel scrolls the page
model: "https://example.com/pavilion.ifc",
lazy: true, // nothing loads until the reader asks
poster: "/img/pavilion.jpg",
posterTitle: "The pavilion, live",
aspectRatio: "16/10", // no layout shift from poster to viewer
background: "auto", // paper on a light page, studio on a dark one
turntable: true, // a slow turn until the reader takes over
fullscreenButton: true,
validate: false,
});Eine Geschichte, die mit dem Text läuft
Den Viewer fixieren und jeden Absatz die Einstellung wählen lassen: Wände isolieren, herumschwenken, die Abendsonne einblenden.
// <div id="figure" style="position:sticky;top:1rem"></div>
// <p id="s1">…</p> <p id="s2">…</p> <p id="s3">…</p>
const viewer = new IfcViewer("#figure", { ui: "article", aspectRatio: "16/10", model: url });
viewer.bindSteps([
{ el: "#s1", frame: { view: "iso" } },
{ el: "#s2", isolate: "IfcWall", frame: { azimuth: 225, elevation: 20 } },
{ el: "#s3", isolate: null, solar: { active: true, date: "06-21", time: "19:30" } },
]);Ein Live-Stadtzwilling auf Ihrer Seite
Eine veröffentlichte Szene öffnen, auf ihre Alarme und Klicks reagieren, eine eigene Live-Quelle daneben hinzufügen und einen Link zum Ergebnis weitergeben.
const viewer = new IfcViewer("#twin", {
ui: "client",
scene: "https://www.ifcvieweronline.eu/scenes/barcelona-placa-catalunya.scene.json",
});
viewer.on("alert", (a) => { if (a.kind === "start") notify(a.name + ": " + a.rule) });
viewer.on("layer-feature-picked", (f) => showCard(f.layer, f.properties));
// Your own live source next to the scene's (GeoJSON with CORS, every 60 s)
await viewer.addLayer({ url: "https://example.org/sensors.geojson", live: 60 });
const { link, sources } = await viewer.exportScene({ title: "Our twin" });Ein Produktdatenblatt in Ihrer App
Version fixieren, die Datei übergeben und zeigen, was der Hersteller in den Typ geschrieben hat: U-Wert, Windlastklasse, Hersteller.
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";
const viewer = await IfcViewer.create("#product-3d", {
ui: "embed", // 3D + properties, Validate and Measure only
panels: ["properties"],
lang: "es",
});
const bytes = await fetch(product.ifcUrl).then(r => r.arrayBuffer());
await viewer.add("V-70-PR.ifc", bytes); // framed for you
// The product, by class (or by its GlobalId) — no expressIDs to know
const [win] = await viewer.findElements({ ifcClass: "IfcWindow" });
// What the manufacturer put in the type (IfcWindowType → HasPropertySets)
const el = await viewer.getElement(win.globalId);
const pset = (name) => el.effectivePropertySets.find(s => s.name === name);
const uw = pset("Pset_WindowCommon")?.properties.find(p => p.name === "ThermalTransmittance");
console.log(el.typeName, el.globalId, uw?.value, uw?.unit); // "Ventana V-70 practicable" … 1.2 "W/(m²·K)"
const { qualityScore } = await viewer.validate(); // Health Score, on demand
// Declared performance per EN 14351-1: wind, water, acoustics, Uw, g / τv, DoP
const en = await viewer.checkEir("builtin-en14351-1");
const missing = en.specs.filter(s => s.status === "fail").map(s => s.name);An deine Marke anpassen
Den Viewer mit einer einzigen Akzentfarbe an dein Produkt angleichen.
new IfcViewer("#viewer", { accent: "#3fa866" });
// …or on the web component:
// <ifc-viewer accent="#3fa866" model="…"></ifc-viewer>Sprachauswahl
Eine Auswahl aus den mitgelieferten Sprachen des Viewers bauen.
// build a <select> from the bundled languages
IfcViewer.LANGUAGES.forEach(({ code, label }) => addOption(code, label));
// switch at runtime
viewer.setLanguage("ja");Health Score
Ablauf der Validierung
validate: true validiert jedes Modell, sobald es geladen ist; validate() startet sie auf Anforderung und löst mit dem Ergebnis auf. Was getValidation() wann liefert:
| Wann | getValidation() liefert |
|---|---|
| Vor jeder Validierung | null — getValidationStatus() meldet idle. |
| Während des ersten Laufs | null — getValidationStatus() meldet running mit Fortschritt; validation-started wurde gesendet. |
| Nach einem Lauf | { qualityScore, errors, warnings, info, total, status: 'done', modelId }. Bei mehreren Modellen das Gesamtergebnis (modelId: null). |
| Während eines neueren Laufs | Die vorherigen Zahlen, mit status: 'running'. |
| Nach einem Fehlschlag | Die vorherigen Zahlen mit status: 'error' und error — oder null, wenn es keine gab. validation-failed wurde gesendet, validate() hat abgelehnt. |
Versionen
Eine Version fixieren
Ab 1.18.0 wird jede Version zusätzlich unter einem Pfad veröffentlicht, der sich nie ändert. In Produktion fixieren; der bewegliche Pfad folgt der neuesten Version.
<script type="module">
import { IfcViewer } from "https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js";
</script>| Pfad | Caching | Einsatz |
|---|---|---|
/sdk/ifc-viewer.es.js | bei jedem Laden neu validiert | Zum Ausprobieren — immer die neueste. |
/sdk/1.18.0/ifc-viewer.es.js | unveränderlich, ein Jahr im Cache | Produktion — der getestete Code ist der Code, der läuft. |
Welchen Viewer öffnet ein fixiertes SDK?
Der iframe ist der Live-Viewer derselben Origin, keine eingefrorene Kopie: Ein fixiertes SDK behält seine API und sein Verhalten, während der Viewer darin besser wird. Der Viewer beantwortet jeden Befehl jedes veröffentlichten SDK — der Build prüft das —, also funktioniert auch eine alte Fixierung weiter.
Auch den Inhalt fixieren
versions.json neben dem SDK listet den Subresource-Integrity-Hash jeder Version:
<script type="module" src="https://www.ifcvieweronline.eu/sdk/1.18.0/ifc-viewer.es.js"
integrity="sha384-WsT4d0DS1oqva4rLWCGBDrx50uZVA3B95IDWbfxaFt8lnjs6NgOrjKNcWQL57/0z" crossorigin="anonymous"></script>Änderungsprotokoll
1.18.0 — Katalogobjekte in einer Host-App. getElement() liefert die GlobalId und die Daten des Typs (typeName, typeProperties mit Einheiten, effectivePropertySets; IFC4-Typen und IFC2x3-Styles). validate(), getValidationStatus(), validation-started / validation-failed, und validate: true validiert nach jedem Laden wirklich. Automatisches Einpassen nach dem Laden, fit() und frame() mit Rand für kleine Objekte, frame(elementId), und setView() bewegt die Kamera wieder. ui: 'embed' mit toolbar und tools. Fixierte Builds unter /sdk/1.18.0/. findElements(), GlobalIds für getElement() / select() / element-selected, Rahmen- und Flügelsätze von Fenstern und Türen und die Materialien des Typs; in ui: 'embed' hält das Eigenschaftspanel das gewählte Objekt im Blick. checkEir('builtin-en14351-1') prüft die erklärten Leistungen von Fenstern und Außentüren nach EN 14351-1 — Windlast, Schlagregendichtheit, Schallschutz, Wärmedurchgangskoeffizient, g und τv, Hersteller und Produktcode — in Standard-IFC-Property-Sets; EIR-Regeln erhalten where (nur Elemente mit einem Eigenschaftswert) und optional.
Hilfe
Fehlerbehebung
Die wenigen Dinge, über die man stolpert.
Es wird nichts gerendert
Prüfe der Reihe nach: ob der Viewer ready erreicht hat, ob das Modell geladen wurde (model-loaded statt model-error), ob der Container eine Höhe ungleich null hat und ob die Byte-Quelle gültiges IFC ist. Die Meldung von model-error nennt die Ursache.
Beim Laden eines Modells kommt ein CORS-Fehler
CORS betrifft nur addFromUrl(url). Wenn du die Bytes kontrollierst, lade sie selbst und rufe add(name, bytes) auf — CORS greift nicht, weil du die Daten direkt übergibst.
Das iframe bleibt leer oder lädt nicht
Die App muss einbettbar sein — kein X-Frame-Options: DENY und keine restriktive frame-ancestors-CSP auf dem Host. Unsere gehostete App erlaubt das Einbetten.
Mein ArrayBuffer ist nach add() leer
add() transferiert den Buffer für eine Übergabe ohne Kopie, wodurch er in deinem Code detached wird. Übergib eine Kopie (bytes.slice(0)), wenn du die Bytes danach noch brauchst.
Ladevorgänge oder Abfragen laufen in einen Timeout
add()/addFromUrl() werden nach loadTimeout abgelehnt (Standard 120 s; 0 deaktiviert). Abfragen nach 30 s, checkIds nach 120 s. Große Modelle auf langsamen Geräten brauchen eventuell ein höheres loadTimeout.
Welche Browser werden unterstützt?
Jeder moderne Evergreen-Browser mit WebAssembly und ES-Modulen — Chrome, Edge, Firefox, Safari. Kein Internet Explorer.
Auf meinem lokalen Dev-Server kommt ein 404
Ein SPA-Dev-Server kann /sdk/ mit seinem HTML-Fallback abfangen. Teste gegen einen echten Build oder ein statisches Hosting, oder importiere das Modul /sdk/ifc-viewer.es.js direkt.