Metainformationen zur Seite
ioBroker VIS in Homarr – responsive, zentriert und HiDPI
Zweck
Die ioBroker-VIS-Ansicht trash wird als iFrame in einer Homarr-Kachel angezeigt. Sie passt ihre Größe automatisch an die Kachel an und bleibt dabei mittig ausgerichtet. Canvas-Gauges werden mit höherer Pixeldichte gezeichnet, damit sie auch auf anderen Monitoren scharf aussehen.
Wichtig: Das VIS-Projekt enthält auch andere Ansichten (z. B. für den MagicMirror). Deshalb dürfen Hintergrund, Scrollverhalten und Skalierung nicht global auf alle Views angewendet werden. Die Homarr-spezifischen Regeln werden nur aktiv, wenn trash die tatsächlich angezeigte VIS-View ist.
Voraussetzungen und Einbau
- ioBroker VIS mit Ansicht
trash; diese wird über das Homarr-iFrame aufgerufen. - In den VIS-Ansichtseinstellungen die gewünschte Breite/Höhe festlegen. Derzeit: 400 × 480 px. Das JavaScript liest die Werte normalerweise automatisch aus.
- JavaScript im VIS-Projekt bei den Skripten hinterlegen. Bisherigen Skalierungs-/HiDPI-Code vollständig ersetzen, nicht zusätzlich einfügen.
- CSS im VIS-Projekt unter Projekt-CSS hinterlegen. Vorheriges Homarr-CSS vollständig ersetzen, damit keine alten globalen Regeln übrig bleiben.
- In Homarr das iFrame-Widget zur View
trasheinbinden und Allow scrolling deaktiviert lassen. - Nach dem Speichern Homarr und MagicMirror vollständig neu laden.
JavaScript – Skalierung, Zentrierung und HiDPI
Der Code läuft nur in einem iFrame und nicht im VIS-Editor. Die Prüfung über vis.activeView verhindert, dass eine andere View angepasst wird, auch wenn trash durch alwaysRender bereits im Hintergrund vorhanden ist.
/** * ioBroker VIS (Ansicht "trash") in Homarr * -------------------------------------- * - Gesamte VIS proportional an die iFrame-Größe anpassen. * - Inhalt horizontal und vertikal zentrieren. * - Alle Canvas-Gauges automatisch in höherer Auflösung zeichnen. * - Keine festen Widget-IDs; neu hinzugefügte Gauges werden mit erfasst. * * Einbau: VIS-Editor -> Projekt -> Skripte / JavaScript. * Die Funktion läuft nur in einem iFrame und nur für die aktive View "trash". * Andere Views, z. B. für MagicMirror, bleiben unverändert. */ (function () { // Nicht in einem eigenständigen Browserfenster oder im VIS-Editor ausführen. if (window.self === window.top) return; if (window.vis && window.vis.editMode) return; // Verhindert eine doppelte Registrierung der Beobachter bei erneutem Laden. if (window.trashAutoScaleActive) return; window.trashAutoScaleActive = true; // Name der VIS-Ansicht. Nur bei Umbenennung der Ansicht ändern. const VIEW_NAME = "trash"; // Ersatzwerte, falls die VIS-Einstellungen noch nicht verfügbar sind. // Aktuelle Ansichtsgröße: 400 x 480 Pixel (wird normalerweise automatisch gelesen). const FALLBACK_WIDTH = 400; const FALLBACK_HEIGHT = 480; // HiDPI: höhere Werte = schärfer, aber größerer Grafik-/Speicherbedarf. const MAX_PIXEL_RATIO = 4; let updatePending = false; // Begrenzt Layout-Berechnungen auf einen pro Frame. let canvasClass = null; // Canvas-Klasse der geladenen Gauge-Bibliothek. let currentPixelRatio = 1; // Tatsächlich verwendeter HiDPI-Faktor. let redrawTimer = null; // Entprellt das Neuzeichnen bei Größenänderungen. /** * Erhöht die interne Zeichenauflösung der Canvas-Gauges. * Die sichtbaren Abmessungen der Widgets bleiben dabei unverändert. * * Wichtig: Die Bibliothek stellt "SmartCanvas" im Browser nicht zwingend * als window.SmartCanvas bereit. Deshalb ermitteln wir die Klasse über * das bereits erzeugte Gauge-Objekt eines beliebigen Canvas-Widgets. * Danach gilt der neue Faktor automatisch für alle Gauges dieser Klasse. */ function updateCanvasResolution(scale) { let Canvas = canvasClass; // Bibliotheksklasse einmalig über ein vorhandenes Gauge finden. if (!Canvas) { const view = document.getElementById("visview_" + VIEW_NAME); if (!view || !window.jQuery) return; const canvases = view.querySelectorAll("canvas.vis-canvas-gauges"); for (const element of canvases) { const widget = element.closest(".vis-widget"); if (!widget) continue; const gauge = window.jQuery(widget).data("gauge"); if ( gauge && gauge.canvas && typeof gauge.canvas.constructor.redraw === "function" ) { Canvas = gauge.canvas.constructor; break; } } } // Gauges noch nicht geladen: Beim nächsten Layout-Update erneut prüfen. if (!Canvas) return; // Bildschirm-Pixeldichte + Vergrößerung der gesamten VIS berücksichtigen. // Untergrenze 3x; Obergrenze 4x gegen unnötigen Ressourcenverbrauch. const dpr = window.devicePixelRatio || 1; const desired = Math.min( MAX_PIXEL_RATIO, Math.max(3, dpr * Math.max(1, scale) * 1.25) ); const nextRatio = Math.ceil(desired * 4) / 4; let installedNow = false; // Den zentralen PixelRatio-Getter der tatsächlichen Klasse einmal ersetzen. if (canvasClass !== Canvas) { const descriptor = Object.getOwnPropertyDescriptor(Canvas, "pixelRatio"); if (!descriptor || !descriptor.configurable) { console.warn("Canvas-Gauges: Pixeldichte nicht anpassbar"); return; } try { Object.defineProperty(Canvas, "pixelRatio", { configurable: true, get: function () { return currentPixelRatio; } }); canvasClass = Canvas; installedNow = true; } catch (error) { console.warn("Canvas-Gauges: HiDPI-Aktivierung fehlgeschlagen", error); return; } } // Ohne Änderung der benötigten Pixeldichte nicht erneut zeichnen. if ( !installedNow && Math.abs(nextRatio - currentPixelRatio) < 0.01 ) { return; } currentPixelRatio = nextRatio; // Kurze Verzögerung vermeidet viele Redraws beim Ziehen der Homarr-Kachel. clearTimeout(redrawTimer); redrawTimer = setTimeout(function () { try { canvasClass.redraw(); console.info("VIS Canvas-Gauges HiDPI:", currentPixelRatio + "x"); } catch (error) { console.warn("Canvas-Gauges: Neuzeichnen fehlgeschlagen", error); } }, 220); } /** * Passt die aktive Homarr-VIS proportional in die iFrame-Fläche ein. * * WICHTIG: Projekt-Skripte laufen in ALLEN Views desselben Projekts. * Deshalb wird die Skalierung ausschließlich für die gerade aktive * View "trash" ausgeführt. Das ist auch bei "alwaysRender" notwendig: * Eine andere View kann im DOM vorhanden sein, ohne angezeigt zu werden. */ function scaleView() { updatePending = false; // Die aktive VIS-Ansicht ist zuverlässiger als nur der URL-Hash. // Hash dient als Fallback, solange VIS noch nicht initialisiert ist. const activeView = window.vis && window.vis.activeView; const isTrashView = activeView ? activeView === VIEW_NAME : decodeURIComponent(window.location.hash.substring(1)) === VIEW_NAME; // Die CSS-Klasse schaltet NUR für die aktive Ziel-View die // Hintergrund-/Überlaufregeln frei. Andere Views bleiben unberührt. document.documentElement.classList.toggle("homarr-trash-embed", isTrashView); document.body.classList.toggle("homarr-trash-embed", isTrashView); // Nicht versuchen, eine unsichtbare, vorgeladene View zu skalieren. if (!isTrashView) return; const container = document.getElementById("vis_container"); const view = document.getElementById("visview_" + VIEW_NAME); if (!container || !view) return; // Größe des tatsächlich verfügbaren VIS-Containers (Homarr-iFrame). const width = container.clientWidth; const height = container.clientHeight; if (width <= 0 || height <= 0) return; // Originalgröße direkt aus den VIS-Ansichtseinstellungen beziehen. // Bei einer späteren Änderung der VIS-Auflösung keine Code-Anpassung nötig. const settings = window.vis && window.vis.views && window.vis.views[VIEW_NAME] && window.vis.views[VIEW_NAME].settings; const baseWidth = parseInt(settings && settings.sizex, 10) || FALLBACK_WIDTH; const baseHeight = parseInt(settings && settings.sizey, 10) || FALLBACK_HEIGHT; // "contain": gesamte VIS sichtbar halten, niemals verzerren. const scale = Math.min(width / baseWidth, height / baseHeight); // Freie Fläche links/rechts und oben/unten gleichmäßig verteilen. const offsetX = (width - baseWidth * scale) / 2; const offsetY = (height - baseHeight * scale) / 2; // Ausgangsposition und Ausgangsabmessungen explizit setzen. view.style.position = "absolute"; view.style.left = "0px"; view.style.top = "0px"; view.style.width = baseWidth + "px"; view.style.height = baseHeight + "px"; // VIS hat standardmäßig min-width/min-height: 100 %. // Das würde unsere Größenberechnung beim Verkleinern verfälschen. view.style.minWidth = "0px"; view.style.minHeight = "0px"; // Zentrierung und Skalierung in einer Transformation kombinieren. view.style.transformOrigin = "top left"; view.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`; // Den Canvas-Gauges ausreichend interne Bildpunkte geben. updateCanvasResolution(scale); } // Mehrere unmittelbar aufeinanderfolgende Änderungen zusammenfassen. function requestUpdate() { if (updatePending) return; updatePending = true; requestAnimationFrame(scaleView); } function initialize() { const container = document.getElementById("vis_container"); if (!container) return; // Homarr-iFrame/Browserfenster wird größer oder kleiner. window.addEventListener("resize", requestUpdate); // Ansicht per URL-Hash oder über VIS-Navigation gewechselt: // CSS-Klasse aktualisieren und nur "trash" skalieren. window.addEventListener("hashchange", requestUpdate); if (window.jQuery) { window.jQuery(window).on("viewChanged", requestUpdate); } // VIS-Container ändert sich, auch ohne klassisches window.resize. if (typeof ResizeObserver !== "undefined") { new ResizeObserver(requestUpdate).observe(container); } // Widgets werden beim VIS-Start ggf. verzögert eingefügt. // So finden wir die Gauge-Bibliothek auch nach dem ersten Rendern. new MutationObserver(requestUpdate).observe(container, { childList: true, subtree: true }); requestUpdate(); } // Erst starten, wenn der HTML-Dokumentbaum verfügbar ist. if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initialize, { once: true }); } else { initialize(); } })();
Projekt-CSS – ausschließlich Homarr-View
Globale Selektoren wie html, body und #vis_container waren der Grund dafür, dass auch die MagicMirror-View verändert wurde. Hier werden sie nur bei aktivierter Klasse homarr-trash-embed angewendet. Die beiden Trashschedule-Regeln sind direkt auf #visview_trash begrenzt.
/* ======================================================================= ioBroker VIS -> Homarr (View "trash") ======================================================================= Dieses CSS wird im VIS-PROJEKT gespeichert, wirkt aber auf die Dokument-/Container-Ebene ausschließlich dann, wenn das JavaScript die Klasse "homarr-trash-embed" auf <html> und <body> gesetzt hat. Andere Views (z. B. MagicMirror) behalten ihren eigenen Hintergrund, ihre Abstände und ihr Überlauf-/Scrollverhalten. ======================================================================= */ /* 1. Keine Seitenränder im Homarr-iFrame. */ html.homarr-trash-embed, body.homarr-trash-embed, body.homarr-trash-embed .mainWrapper { margin: 0 !important; padding: 0 !important; } /* 2. Kein sichtbarer Überlauf/keine Scrollbalken in Homarr. Nicht global auf html, body oder #vis_container anwenden! */ html.homarr-trash-embed, body.homarr-trash-embed, body.homarr-trash-embed .mainWrapper, body.homarr-trash-embed #vis_container, body.homarr-trash-embed .no-scrollbars { overflow: hidden !important; } /* 3. Ein durchgängiger Hintergrund verhindert weiße Ränder beim Skalieren. Der Farbwert gilt nur während die View "trash" aktiv ist. */ html.homarr-trash-embed, body.homarr-trash-embed, body.homarr-trash-embed .mainWrapper, body.homarr-trash-embed #vis_container, body.homarr-trash-embed #visview_trash { background-color: #25262b !important; } /* 4. Trashschedule in der View "trash": Alle Tonnen bleiben in einer Zeile. Absichtlich auf die View beschränkt, aber NICHT zusätzlich auf "homarr-trash-embed": so bleibt das Widget-Layout stabil. */ #visview_trash .trashschedule-widget .trashtypes { white-space: nowrap !important; width: max-content !important; transform-origin: top left !important; } /* 5. Mülltonnen etwas enger zusammenrücken. Andere Trashschedule-Widgets (z. B. im MagicMirror) sind nicht betroffen. */ #visview_trash .trashschedule-widget .trashtype { margin-left: 5px !important; margin-top: 0 !important; } /* Für Canvas-Gauges ist kein zusätzliches CSS notwendig. Die schärfere interne Darstellung übernimmt das JavaScript. */
Erklärung der Funktionen
| Bereich | Funktion |
|---|---|
| View-Prüfung | vis.activeView bestimmt, ob trash wirklich angezeigt wird. Der URL-Hash ist nur Ersatz, falls die VIS noch nicht initialisiert ist. |
| CSS-Aktivierung | Die Klasse homarr-trash-embed wird nur für die aktive View trash gesetzt. Damit bleibt MagicMirror unbeeinflusst. |
| Responsive Skalierung | scale = min(iFrame-Breite / VIS-Breite, iFrame-Höhe / VIS-Höhe). Die ganze Ansicht bleibt vollständig sichtbar und behält ihre Proportionen. |
| Zentrierung | Die freie Fläche wird rechts/links sowie oben/unten gleichmäßig verteilt. |
| VIS-Größe | Die Maße aus den View-Einstellungen (sizex / sizey) haben Vorrang. Fallback: 400 × 480 px. |
| Canvas-HiDPI | Die Canvas-Gauges-Bibliothek wird über ein vorhandenes Gauge ermittelt; alle Gauges dieser Bibliothek zeichnen mit höherer interner Auflösung. |
| Ressourcen | requestAnimationFrame fasst Layout-Updates zusammen; ein kurzer Timer entprellt Canvas-Neuzeichnungen. |
| Neue Widgets | Werden automatisch mit der gesamten View skaliert. Neue Gauges der gleichen Bibliothek nutzen den HiDPI-Faktor. |
| CSS Trashschedule | Kein Zeilenumbruch, geringere Abstände – ausschließlich innerhalb von #visview_trash. |
Einstellungen und Wartung
| Konstante | Wert | Bedeutung |
|---|---|---|
VIEW_NAME | trash | Name der Homarr-VIS-View; nur bei einer Umbenennung anpassen. |
FALLBACK_WIDTH | 400 | Ersatzbreite, falls VIS-Größen noch nicht verfügbar sind. |
FALLBACK_HEIGHT | 480 | Ersatzhöhe, falls VIS-Größen noch nicht verfügbar sind. |
MAX_PIXEL_RATIO | 4 | Obergrenze der internen Canvas-Auflösung. Höhere Werte benötigen mehr Speicher/Grafikleistung. |
#25262b | Hintergrund | Farbwert für die Homarr-View und die umgebenden iFrame-Ebenen. |
- Ansichtsgröße ändern: Breite/Höhe vorzugsweise nur im VIS-Editor ändern. Die Fallback-Werte müssen normalerweise nicht angepasst werden.
- Neue Widgets: Keine Widget-IDs ins Script aufnehmen. Widgets müssen innerhalb der im VIS-Editor festgelegten Fläche liegen, damit sie beim Skalieren nicht abgeschnitten werden.
- Andere Views: Kein globales CSS ohne
homarr-trash-embedergänzen. Die Viewtrashbleibt speziell für Homarr konfiguriert; MagicMirror verwendet seine eigene View. - HiDPI: Die Anpassung nutzt interne Funktionen von
vis-canvas-gauges. Nach größeren Adapterupdates Darstellung kontrollieren. Andere Canvas-Widget-Bibliotheken sind nicht automatisch abgedeckt. - Projekt-Skripte gelten projektweit: Darum die aktive View prüfen und die Schutzabfragen am Scriptanfang beibehalten.
Kontrolle / Fehlerdiagnose
- Homarr: Kachel in Breite/Höhe verändern: gesamte VIS sollte proportional und mittig bleiben; keine weißen Ränder oder Scrollbalken.
- Canvas-Gauges: Auf dem betroffenen Monitor die Skalenstriche und Zahlen prüfen. Bei erfolgreichem Neuzeichnen erscheint in der Entwicklerkonsole des VIS-iFrames z. B.
VIS Canvas-Gauges HiDPI: 3x. - MagicMirror: Seine andere View muss ihre ursprüngliche Positionierung, Hintergrundfarbe und Widget-Abstände behalten.
- Wenn Homarr verrutscht: Prüfen, ob die aktive View
trashist, die CSS-Klassehomarr-trash-embedgesetzt wird und keine alten globalen CSS-Regeln übrig sind. - Wenn Mülltonnen umbrechen: Kontrollieren, dass die Trashschedule-Selektoren auf
#visview_trash .trashschedule-widgetbeginnen und nicht vom globalen Homarr-Klassenstatus abhängig sind. - Wenn der iFrame vergrößert statt skaliert erscheint: Die Layoutberechnung wurde möglicherweise nicht ausgeführt; Ansichtsnamen und JavaScript-Konsole prüfen.
Sicherung
Vor größeren Änderungen das VIS-Projekt bzw. die Konfigurationsdatei vis-views.json sichern. Diese Dokumentation beschreibt den zuletzt in Homarr und MagicMirror erfolgreich getesteten Stand.