Metainformationen zur Seite
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| iobroker_vis_in_homarr_skalierung_zentrierung_und_hidpi [10.10.2026] – angelegt kolja | iobroker_vis_in_homarr_skalierung_zentrierung_und_hidpi [10.10.2026] (aktuell) – gelöscht kolja | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== ioBroker VIS in Homarr – Skalierung, Zentrierung und HiDPI ====== | ||
| - | |||
| - | ===== Zweck ===== | ||
| - | Die VIS-Ansicht **trash** wird als iFrame in Homarr dargestellt. Beim Verändern der Homarr-Kachel werden alle Widgets gemeinsam proportional skaliert und horizontal/ | ||
| - | |||
| - | ^ Einstellung ^ Wert / Funktion ^ | ||
| - | | VIS-Ansicht | '' | ||
| - | | VIS-Auflösung | 400 × 480 px (wird aus VIS gelesen) | | ||
| - | | Hintergrund | ''# | ||
| - | | Max. HiDPI-Faktor | '' | ||
| - | | Startbedingung | Nur im iFrame, nicht im VIS-Editor | | ||
| - | | Anpassungen bei neuen Widgets | Keine JavaScript-Widget-IDs notwendig | | ||
| - | |||
| - | ===== Einrichtung ===== | ||
| - | - Im VIS-Editor das entsprechende Projekt öffnen. | ||
| - | - Unter **Projekt / Skripte (JavaScript)** den bisherigen Skalierungs-Code vollständig durch den folgenden JavaScript-Code ersetzen. Nicht doppelt einfügen. | ||
| - | - Unter **Projekt-CSS** den bisherigen CSS-Code für dieses Homarr-Layout durch den nachfolgenden CSS-Code ersetzen. | ||
| - | - Änderungen speichern und Homarr vollständig neu laden. | ||
| - | - Homarr-Kachel kleiner/ | ||
| - | |||
| - | ===== JavaScript – automatische Skalierung, Zentrierung und scharfe Gauges ===== | ||
| - | <code javascript> | ||
| - | /** | ||
| - | * ioBroker VIS (Ansicht " | ||
| - | * -------------------------------------- | ||
| - | * - 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 bewusst nur in einem iFrame, nicht im VIS-Editor. | ||
| - | */ | ||
| - | (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 = " | ||
| - | |||
| - | // Ersatzwerte, | ||
| - | // Aktuelle Ansichtsgröße: | ||
| - | const FALLBACK_WIDTH = 400; | ||
| - | const FALLBACK_HEIGHT = 480; | ||
| - | |||
| - | // HiDPI: höhere Werte = schärfer, aber größerer Grafik-/ | ||
| - | const MAX_PIXEL_RATIO = 4; | ||
| - | |||
| - | let updatePending = false; | ||
| - | 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 " | ||
| - | * 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(" | ||
| - | if (!view || !window.jQuery) return; | ||
| - | |||
| - | const canvases = view.querySelectorAll(" | ||
| - | |||
| - | for (const element of canvases) { | ||
| - | const widget = element.closest(" | ||
| - | if (!widget) continue; | ||
| - | |||
| - | const gauge = window.jQuery(widget).data(" | ||
| - | |||
| - | if ( | ||
| - | gauge && gauge.canvas && | ||
| - | typeof gauge.canvas.constructor.redraw === " | ||
| - | ) { | ||
| - | 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, | ||
| - | |||
| - | if (!descriptor || !descriptor.configurable) { | ||
| - | console.warn(" | ||
| - | return; | ||
| - | } | ||
| - | |||
| - | try { | ||
| - | Object.defineProperty(Canvas, | ||
| - | configurable: | ||
| - | get: function () { | ||
| - | return currentPixelRatio; | ||
| - | } | ||
| - | }); | ||
| - | canvasClass = Canvas; | ||
| - | installedNow = true; | ||
| - | } catch (error) { | ||
| - | console.warn(" | ||
| - | 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(" | ||
| - | } catch (error) { | ||
| - | console.warn(" | ||
| - | } | ||
| - | }, 220); | ||
| - | } | ||
| - | |||
| - | /** | ||
| - | * Passt die ganze VIS proportional in die aktuelle iFrame-Fläche ein. | ||
| - | * Die Ansicht behält ihr Seitenverhältnis und wird in beiden Achsen zentriert. | ||
| - | */ | ||
| - | function scaleView() { | ||
| - | updatePending = false; | ||
| - | |||
| - | const container = document.getElementById(" | ||
| - | const view = document.getElementById(" | ||
| - | 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, | ||
| - | const baseHeight = | ||
| - | parseInt(settings && settings.sizey, | ||
| - | |||
| - | // " | ||
| - | const scale = Math.min(width / baseWidth, height / baseHeight); | ||
| - | |||
| - | // Freie Fläche links/ | ||
| - | const offsetX = (width - baseWidth * scale) / 2; | ||
| - | const offsetY = (height - baseHeight * scale) / 2; | ||
| - | |||
| - | // Ausgangsposition und Ausgangsabmessungen explizit setzen. | ||
| - | view.style.position = " | ||
| - | view.style.left = " | ||
| - | view.style.top = " | ||
| - | view.style.width = baseWidth + " | ||
| - | view.style.height = baseHeight + " | ||
| - | |||
| - | // VIS hat standardmäßig min-width/ | ||
| - | // Das würde unsere Größenberechnung beim Verkleinern verfälschen. | ||
| - | view.style.minWidth = " | ||
| - | view.style.minHeight = " | ||
| - | |||
| - | // Zentrierung und Skalierung in einer Transformation kombinieren. | ||
| - | view.style.transformOrigin = "top left"; | ||
| - | view.style.transform = | ||
| - | `translate(${offsetX}px, | ||
| - | |||
| - | // 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(" | ||
| - | if (!container) return; | ||
| - | |||
| - | // Homarr-iFrame/ | ||
| - | window.addEventListener(" | ||
| - | |||
| - | // VIS-Container ändert sich, auch ohne klassisches window.resize. | ||
| - | if (typeof ResizeObserver !== " | ||
| - | 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 === " | ||
| - | document.addEventListener(" | ||
| - | } else { | ||
| - | initialize(); | ||
| - | } | ||
| - | })(); | ||
| - | </ | ||
| - | |||
| - | ===== Projekt-CSS – Hintergrund und Trashschedule ===== | ||
| - | <code css> | ||
| - | /* ============================================================ | ||
| - | | ||
| - | | ||
| - | |||
| - | /* 1. Seitenränder entfernen: Der iFrame-Inhalt beginnt ohne Abstand. */ | ||
| - | html, | ||
| - | body, | ||
| - | .mainWrapper { | ||
| - | margin: 0 !important; | ||
| - | padding: 0 !important; | ||
| - | } | ||
| - | |||
| - | /* 2. Überlauf ausblenden: Keine Scrollbalken im Homarr-iFrame. | ||
| - | Achtung: html/body wirken auf ALLE Ansichten dieses VIS-Projekts. */ | ||
| - | html, | ||
| - | body, | ||
| - | .mainWrapper, | ||
| - | # | ||
| - | .no-scrollbars { | ||
| - | overflow: hidden !important; | ||
| - | } | ||
| - | |||
| - | /* 3. Einheitlicher Hintergrund auf allen Ebenen. | ||
| - | Verhindert weiße Streifen bei wechselnder iFrame-Größe. | ||
| - | Achtung: html/body und # | ||
| - | html, | ||
| - | body, | ||
| - | .mainWrapper, | ||
| - | # | ||
| - | # | ||
| - | background-color: | ||
| - | } | ||
| - | |||
| - | /* 4. Trashschedule: | ||
| - | Selektor über Widget-Klasse statt ID: auch für weitere | ||
| - | Trashschedule-Widgets innerhalb der Ansicht " | ||
| - | # | ||
| - | white-space: | ||
| - | width: max-content !important; | ||
| - | transform-origin: | ||
| - | } | ||
| - | |||
| - | /* 5. Weniger Abstand: Tonnen kompakter nebeneinander anordnen. */ | ||
| - | # | ||
| - | margin-left: | ||
| - | margin-top: 0 !important; | ||
| - | } | ||
| - | |||
| - | /* Kein zusätzliches CSS für Canvas-Gauges notwendig: | ||
| - | Die höhere Zeichenauflösung regelt das JavaScript. */ | ||
| - | </ | ||
| - | |||
| - | ===== Funktionsprinzip ===== | ||
| - | * **Skalierung: | ||
| - | * **Zentrierung: | ||
| - | * **HiDPI:** Die Canvas-Gauges zeichnen intern mit höherer Pixeldichte; | ||
| - | * **Neue Widgets:** Die VIS-Gesamtskalierung gilt automatisch. Weitere Widgets aus derselben Canvas-Gauges-Bibliothek profitieren ebenfalls von der HiDPI-Einstellung. | ||
| - | * **Mülltonnen: | ||
| - | * **Hintergrund: | ||
| - | |||
| - | ===== Anpassungen und Hinweise ===== | ||
| - | * '' | ||
| - | * '' | ||
| - | * '' | ||
| - | * **Projektweit: | ||
| - | * **Grenze:** Die HiDPI-Funktion ist auf die verwendete '' | ||
| - | * **Fehlersuche: | ||
| - | |||
| - | ===== Wartung ===== | ||
| - | Vor größeren Änderungen VIS-Projekt exportieren/ | ||