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 trash einbinden 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-embed ergänzen. Die View trash bleibt 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

  1. Homarr: Kachel in Breite/Höhe verändern: gesamte VIS sollte proportional und mittig bleiben; keine weißen Ränder oder Scrollbalken.
  2. 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.
  3. MagicMirror: Seine andere View muss ihre ursprüngliche Positionierung, Hintergrundfarbe und Widget-Abstände behalten.
  4. Wenn Homarr verrutscht: Prüfen, ob die aktive View trash ist, die CSS-Klasse homarr-trash-embed gesetzt wird und keine alten globalen CSS-Regeln übrig sind.
  5. Wenn Mülltonnen umbrechen: Kontrollieren, dass die Trashschedule-Selektoren auf #visview_trash .trashschedule-widget beginnen und nicht vom globalen Homarr-Klassenstatus abhängig sind.
  6. 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.

Diese Website verwendet Cookies. Durch die Nutzung der Website stimmen Sie dem Speichern von Cookies auf Ihrem Computer zu. Außerdem bestätigen Sie, dass Sie unsere Datenschutzbestimmungen gelesen und verstanden haben. Wenn Sie nicht einverstanden sind, verlassen Sie die Website.Weitere Information