Handy-Darstellung: Diagramme, Meteogramm und Abstaende

Auf dem Handy war die Seite bisher nur mit der Desktopversion des Browsers
brauchbar. Drei Ursachen, alle nachgemessen bei 375 px:

Die Diagramme standen im Hochformat, 301 x 400 px - die Zeitachse lag auf
der kurzen Seite. Die Hoehe kam nicht aus dem Container: .chart-container
hatte inline height:100% in einem Elternelement ohne Hoehe, und die Canvas
trugen height="400px;", was kein gueltiger Zahlenwert ist. Die Hoehe steht
jetzt im Stylesheet, 400 px am Rechner und 250 px unter 768 px Breite, die
Beschriftung dort 8 statt 10 px.

Das Meteogramm hat sich nicht angepasst, sondern gequetscht. meteoblue legt
sein Bild einmal beim Laden fest und zeichnet es bei einer Breitenaenderung
nicht neu; unter etwa 600 px ueberlagern sich Wochentage und Stundenachse.
Es wird jetzt in mindestens 640 px Breite geladen - auf breiten Karten
gleich in Kartenbreite, dort aendert sich nichts - und, wenn die Karte
schmaler ist, als Ganzes verkleinert. Zuschnitt und Skalierung setzt das
Skript direkt am Element, nicht ueber das Stylesheet: sonst haengt die
Darstellung an einer womoeglich veralteten solar.css im Browser-Cache, und
der breit geladene Rahmen ragt ungeschnitten aus der Karte.

Passend dazu bekommt solar.css eine Versionskennung aus filemtime(), wie sie
die Skripte in footer.php und adminlte.min.css laengst tragen. Ohne sie kam
auf schon einmal besuchten Geraeten die alte Datei aus dem Cache.

Die Statistik-Kacheln lagen in nackten .col ohne Breitenangabe; Bootstrap
laesst sie dann nach Textlaenge umbrechen, mal zwei, mal drei je Zeile und
unterschiedlich breit. Die Breite kommt jetzt aus row-cols-2/row-cols-md-4,
und die Karte fuellt die Zeilenhoehe, damit die Werte trotz ein- oder
zweizeiliger Titel auf einer Linie stehen.

Zuletzt sind die Abstaende unter 768 px enger gefasst - auf .app-content
beschraenkt, damit Seitenleiste, Kopfzeile und Modals unberuehrt bleiben:
Spaltenabstand 24 -> 8 px, Kartenabstand 24 -> 12 px, Polsterung 16 -> 8 px.
Die nutzbare Breite steigt damit von 301 auf 317 px.

Geprueft bei 375 und 1200 px auf Solar, Historie, Heizung, Home und Wetter:
kein Querlauf, am Rechner unveraenderte Werte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 23:05:33 +02:00
co-authored by Claude Opus 5
parent 6aac3326a9
commit fd27fe168e
8 changed files with 137 additions and 19 deletions
+2 -2
View File
@@ -66,7 +66,7 @@ function array_insert($array,$values,$offset) {
if (checkLogin()) {
$html = "<div class='row row-info'>";
$html = "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
$mysql = new mysqli($mysql_server, $mysql_solarUser, $mysql_solarPass, $mysql_solarDB);
$Res = mysqli_query($mysql,$Query);
$ResPrev = mysqli_query($mysql,$PrevQuery);
@@ -84,7 +84,7 @@ if (checkLogin()) {
foreach ($row as $name => $value) {
if(str_starts_with($name,"Autoladung ges.")){
$html .= "</div><hr class='mt-2 mb-3 border-light' />";
$html .= "<div class='row row-info'>";
$html .= "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
}
/*$html .= "<div class='col-12 col-sm-4 col-md-2 col-xl-1'>
<div class='info-box'>
+66
View File
@@ -19,3 +19,69 @@ input[type='range']::-webkit-slider-runnable-track {
background-size: var(--background-size, 0%) 100%;
background-repeat: no-repeat;
}
/* Diagramme (Chart.js). Die Hoehe kommt aus diesem Container, nicht aus dem
canvas - die Charts laufen mit maintainAspectRatio: false. Auf dem Handy
ist der Container nur rund 300 px breit; mit 400 px Hoehe wird aus einer
Zeitreihe ein Hochformat, in dem die Zeitachse die kurze Seite ist. */
.chart-container {
position: relative;
width: 100%;
height: 400px;
}
@media (max-width: 767.98px) {
.chart-container {
height: 250px;
}
}
/* Statistik-Kacheln (ajax/getStats.php). Sie lagen in nackten .col ohne
Breitenangabe - Bootstrap laesst sie dann nach Textlaenge umbrechen, mal
zwei, mal drei je Zeile und unterschiedlich breit. Die Breite kommt jetzt
aus row-cols-* in getStats.php; hier sorgt die volle Kartenhoehe dafuer,
dass die Werte trotz ein- oder zweizeiliger Titel auf einer Linie
stehen. */
.card.stats {
height: 100%;
}
.card.stats .card-body {
flex: 1 1 auto;
}
/* Auf dem Handy gehen von 375 px rund 75 an Innenabstaende verloren, und
zwischen den Karten stehen 24 px. Beides wird hier enger gefasst - nur
im Inhaltsbereich, damit Seitenleiste, Kopfzeile und Modals unberuehrt
bleiben. Die Abstandsklassen von Bootstrap (mb-4) tragen !important,
deshalb steht es auch hier. */
@media (max-width: 767.98px) {
.app-content {
padding: 0 .25rem;
}
.app-content-header {
padding: .5rem .25rem;
}
.app-content .row {
--bs-gutter-x: .5rem;
}
.app-content .card {
margin-bottom: .75rem !important;
}
/* Nur der Standardwert wird kleiner; eigene Angaben im Markup (p-0 fuer
das Realtime-SVG, p-2 fuer die Statistik) gelten weiter. */
.app-content .card-body {
padding: .5rem;
}
/* Eine Kachel ist auf dem Handy rund 150 px breit. In voller Schrift
passt "Verbrauchsersparnis" dort nicht in eine Zeile und wird mitten
im Wort umbrochen. */
.card.stats .card-body {
font-size: .8rem;
}
}
+51 -3
View File
@@ -156,12 +156,60 @@ function bindModalSlider(slider, span, label) {
}
/** Meteoblue-Meteogramm einhängen, sofern die Seite einen Platz dafür hat. */
// Mindestbreite, in der das Meteogramm von meteoblue geladen wird. Der
// Anbieter legt sein Bild einmal beim Laden fest und zeichnet es bei einer
// Breitenaenderung nicht neu; unterhalb von etwa 600 px ueberlagern sich
// Wochentage und Stundenachse zu einem unlesbaren Streifen. Auf schmalen
// Schirmen wird es deshalb in dieser Breite geladen und als Ganzes
// verkleinert - dieselbe Ansicht wie am Rechner, nur kleiner. Ist die Karte
// breiter, wird gleich in Kartenbreite geladen und nichts skaliert.
const METEOGRAM_BREITE = 640;
const METEOGRAM_HOEHE = 500;
function mountMeteogram() {
const target = document.getElementById("meteogram");
if (!target) return;
const ziel = document.getElementById("meteogram");
if (!ziel) return;
// loading="lazy": das Meteogramm ist ein vollstaendiges Fremd-Dokument und
// liegt unterhalb der Falz - es wird erst geladen, wenn man hinscrollt.
target.innerHTML = "<iframe loading='lazy' src='" + meteogramURL + "' frameborder='0' scrolling=NO' allowtransparency='true' sandbox='allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox' style='width: 100%;height: 500px;border: 0;overflow: hidden;'></iframe><!-- DO NOT REMOVE THIS LINK --><a href='https://www.meteoblue.com/de/wetter/woche/index' target='_blank' rel='noopener'></a>";
const stil = window.getComputedStyle(ziel);
const innenBreite = ziel.clientWidth - parseFloat(stil.paddingLeft) - parseFloat(stil.paddingRight);
const ladeBreite = Math.max(METEOGRAM_BREITE, Math.round(innenBreite) || 0);
ziel.innerHTML =
"<div class='meteogram-buehne'>"
+ "<iframe loading='lazy' src='" + meteogramURL + "' frameborder='0' scrolling='no'"
+ " allowtransparency='true'"
+ " sandbox='allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox'"
+ " style='width:" + ladeBreite + "px;height:" + METEOGRAM_HOEHE + "px;border:0;overflow:hidden;'></iframe>"
+ "</div>"
+ "<!-- DO NOT REMOVE THIS LINK -->"
+ "<a href='https://www.meteoblue.com/de/wetter/woche/index' target='_blank' rel='noopener'></a>";
const buehne = ziel.querySelector(".meteogram-buehne");
const rahmen = buehne.querySelector("iframe");
// Zuschnitt und Skalierung stehen hier und nicht im Stylesheet: sonst
// haengt die Darstellung an einer womoeglich veralteten solar.css im
// Browser-Cache, und der breit geladene Rahmen ragte ungeschnitten aus
// der Karte heraus.
buehne.style.overflow = "hidden";
rahmen.style.display = "block";
rahmen.style.transformOrigin = "0 0";
let letzteBreite = 0;
const anpassen = function () {
const breite = buehne.clientWidth;
if (breite === 0 || breite === letzteBreite) return;
letzteBreite = breite;
const faktor = Math.min(1, breite / ladeBreite);
rahmen.style.transform = "scale(" + faktor + ")";
buehne.style.height = Math.round(METEOGRAM_HOEHE * faktor) + "px";
};
anpassen();
// Nur auf Breitenaenderungen reagieren - die Hoehe setzt anpassen() selbst,
// sonst riefe sich der Beobachter gegenseitig auf.
if (window.ResizeObserver) {
new ResizeObserver(anpassen).observe(buehne);
} else {
window.addEventListener("resize", anpassen);
}
}
/**
+5 -1
View File
@@ -48,7 +48,11 @@
// bei DPR 3 rund 75 %.
Chart.defaults.devicePixelRatio = Math.min(1.5, window.devicePixelRatio || 1);
Chart.defaults.font.size = 10;
// Auf schmalen Schirmen ist die Zeichenflaeche nur rund 300 px breit -
// 10 px Schrift nehmen dort zu viel davon weg. Gleiche Schwelle wie die
// flachere Diagrammhoehe in css/solar.css.
const schmalerSchirm = window.matchMedia("(max-width: 767.98px)").matches;
Chart.defaults.font.size = schmalerSchirm ? 8 : 10;
Chart.defaults.animations = false;
Chart.defaults.animation = false;
Chart.defaults.plugins.legend.position = "bottom";
+1 -1
View File
@@ -29,7 +29,7 @@ if (!isset($_GET["floor"]))
<!-- Skip links will be dynamically added by accessibility.js -->
<meta name="supported-color-schemes" content="light dark" />
<link rel="preload" href="./css/adminlte.min.css?v=2" as="style" />
<link rel="stylesheet" href="./css/solar.css" />
<link rel="stylesheet" href="./css/solar.css?v=<?= filemtime("css/solar.css") ?>" />
<!--end::Accessibility Features-->
<!--begin::Fonts-->
+4 -4
View File
@@ -60,8 +60,8 @@
</div>
<div class="card-body pb-1">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<canvas id="heat-chart" height="400px;"></canvas>
<div class="chart-container">
<canvas id="heat-chart"></canvas>
</div>
</div>
</div>
@@ -86,8 +86,8 @@
</div>
<div class="card-body pb-1">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<canvas id="water-chart" height="400px;"></canvas>
<div class="chart-container">
<canvas id="water-chart"></canvas>
</div>
</div>
</div>
+2 -2
View File
@@ -54,8 +54,8 @@ $statistics = [
</div>
<div class="card-body pb-1">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<canvas id="<?= $kind ?>-chart<?= $period == "month" ? "" : "-" . $period ?>" height="400px;"></canvas>
<div class="chart-container">
<canvas id="<?= $kind ?>-chart<?= $period == "month" ? "" : "-" . $period ?>"></canvas>
</div>
</div>
</div>
+6 -6
View File
@@ -111,8 +111,8 @@
</div>
<div class="card-body pb-1">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<canvas id="forecast-chart" height="400px;"></canvas>
<div class="chart-container">
<canvas id="forecast-chart"></canvas>
</div>
</div>
</div>
@@ -142,8 +142,8 @@
</div>
<div class="card-body pb-1">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<canvas id="consumption-chart" height="400px;"></canvas>
<div class="chart-container">
<canvas id="consumption-chart"></canvas>
</div>
</div>
</div>
@@ -172,8 +172,8 @@
</div>
<div class="card-body pb-1">
<div class="chart-container" style="position: relative; height:100%; width:100%">
<canvas id="production-chart" height="400px;"></canvas>
<div class="chart-container">
<canvas id="production-chart"></canvas>
</div>
</div>
</div>