Ein skeuomorpher Versuch, einen Solarrechner im Web mit der Window Controls Overlay API und der Umgebungslichtsensor API nachzubilden.
Die Herausforderung
Ich bin ein Kind der 1980er. Als ich in der weiterführenden Schule war, waren Solarrechner der letzte Schrei. Wir alle bekamen von der Schule einen TI-30X SOLAR und ich erinnere mich gerne daran, wie wir unsere Taschenrechner miteinander verglichen haben, indem wir die Fakultät von 69 berechnet haben, der höchsten Zahl, die der TI-30X verarbeiten konnte. (Die Geschwindigkeitsabweichung war sehr messbar, ich habe immer noch keine Ahnung, warum.)
Jetzt, fast 28 Jahre später, dachte ich, es wäre eine lustige Designcember-Herausforderung, den Rechner in HTML, CSS und JavaScript nachzubauen. Da ich kein Designer bin, habe ich nicht bei null angefangen, sondern mit einem CodePen von Sassja Ceballos.

Installierbar machen
Das war kein schlechter Anfang, aber ich wollte es noch skeuomorpher machen. Der erste Schritt bestand darin, die Website in eine PWA umzuwandeln, damit sie installiert werden kann.
self.addEventListener('install', (event) => {
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
self.clients.claim();
event.waitUntil(
(async () => {
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})(),
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
(async () => {
try {
const response = await event.preloadResponse;
if (response) {
return response;
}
return fetch(event.request);
} catch {
return new Response('Offline');
}
})(),
);
});
Mit Mobilgeräten verschmelzen
Nachdem die App jetzt installierbar ist, besteht der nächste Schritt darin, sie so weit wie möglich in die Betriebssystem-Apps einzubinden. Auf Mobilgeräten kann ich dazu den Anzeigemodus im Web-App-Manifest auf fullscreen festlegen.
{
"display": "fullscreen"
}
Auf Geräten mit einer Kameraaussparung oder Notch sieht die App fantastisch aus, wenn du den Darstellungsbereich so anpasst, dass der Inhalt den gesamten Bildschirm ausfüllt.
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />

Mit Desktop verschmelzen
Auf dem Desktop gibt es eine coole Funktion, die ich verwenden kann: Window Controls Overlay. Damit kann ich Inhalte in der Titelleiste des App-Fensters platzieren. Der erste Schritt besteht darin, die Fallback-Sequenz für den Anzeigemodus zu überschreiben, damit zuerst window-controls-overlay verwendet wird, wenn verfügbar.
{
"display_override": ["window-controls-overlay"]
}
Dadurch wird die Titelleiste effektiv ausgeblendet und der Inhalt wird in den Bereich der Titelleiste verschoben, als wäre die Titelleiste nicht vorhanden. Meine Idee ist, die skeuomorphe Solarzelle in die Titelleiste zu verschieben und den Rest der Benutzeroberfläche des Rechners entsprechend nach unten zu verschieben. Das kann ich mit CSS tun, das die Umgebungsvariablen titlebar-area-* verwendet. Sie werden feststellen, dass alle Selektoren die Klasse wco haben. Das wird in einigen Absätzen weiter unten relevant sein.
#calc_solar_cell.wco {
position: fixed;
left: calc(0.25rem + env(titlebar-area-x, 0));
top: calc(0.75rem + env(titlebar-area-y, 0));
width: calc(env(titlebar-area-width, 100%) - 0.5rem);
height: calc(env(titlebar-area-height, 33px) - 0.5rem);
}
#calc_display_surface.wco {
margin-top: calc(env(titlebar-area-height, 33px) - 0.5rem);
}
Als Nächstes muss ich entscheiden, welche Elemente ziehbar sein sollen, da die Titelleiste, die ich normalerweise zum Ziehen verwenden würde, nicht verfügbar ist. Im Stil eines klassischen Widgets kann ich sogar den gesamten Rechner ziehbar machen, indem ich (-webkit-)app-region: drag anwende, mit Ausnahme der Schaltflächen, die (-webkit-)app-region: no-drag erhalten, damit sie nicht zum Ziehen verwendet werden können.
#calc_inside.wco,
#calc_solar_cell.wco {
-webkit-app-region: drag;
app-region: drag;
}
button {
-webkit-app-region: no-drag;
app-region: no-drag;
}
Im letzten Schritt wird die App so konfiguriert, dass sie auf Änderungen am Fenstersteuerungs-Overlay reagiert. Bei einem echten Ansatz der progressiven Verbesserung lade ich den Code für diese Funktion nur, wenn der Browser sie unterstützt.
if ('windowControlsOverlay' in navigator) {
import('/wco.js');
}
Wenn sich die Geometrie des Overlays für die Fenstersteuerung ändert, passe ich die App an, damit sie so natürlich wie möglich aussieht. Es empfiehlt sich, dieses Ereignis zu entprellen, da es häufig ausgelöst werden kann, wenn der Nutzer die Größe des Fensters ändert. Ich wende die Klasse wco auf einige Elemente an, damit mein CSS von oben greift, und ändere auch die Designfarbe. Ich kann prüfen, ob das Overlay für die Fenstersteuerung sichtbar ist, indem ich die Property navigator.windowControlsOverlay.visible überprüfe.
const meta = document.querySelector('meta[name="theme-color"]');
const nodes = document.querySelectorAll(
'#calc_display_surface, #calc_solar_cell, #calc_outside, #calc_inside',
);
const toggleWCO = () => {
if (!navigator.windowControlsOverlay.visible) {
meta.content = '';
} else {
meta.content = '#385975';
}
nodes.forEach((node) => {
node.classList.toggle('wco', navigator.windowControlsOverlay.visible);
});
};
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
navigator.windowControlsOverlay.ongeometrychange = debounce((e) => {
toggleWCO();
}, 250);
toggleWCO();
Jetzt habe ich ein Rechner-Widget, das sich fast wie das klassische Winamp mit einem der alten Winamp-Designs anfühlt. Ich kann den Rechner jetzt frei auf meinem Desktop platzieren und die Fenstersteuerung aktivieren, indem ich auf das Chevron-Symbol oben rechts klicke.

Eine funktionierende Solarzelle
Für den ultimativen Geek-Faktor musste ich die Solarzelle natürlich auch zum Laufen bringen. Der Rechner sollte nur funktionieren, wenn genügend Licht vorhanden ist. Ich habe das so modelliert, dass ich die CSS-opacity der Ziffern auf dem Display über eine CSS-Variable --opacity festlege, die ich über JavaScript steuere.
:root {
--opacity: 0.75;
}
#calc_expression,
#calc_result {
opacity: var(--opacity);
}
Um zu erkennen, ob genügend Licht vorhanden ist, damit der Rechner funktioniert, verwende ich die AmbientLightSensor API. Damit diese API verfügbar ist, musste ich das Flag #enable-generic-sensor-extra-classes in about:flags festlegen und die Berechtigung 'ambient-light-sensor' anfordern. Wie zuvor verwende ich die progressive Verbesserung, um den relevanten Code nur dann zu laden, wenn die API unterstützt wird.
if ('AmbientLightSensor' in window) {
import('/als.js');
}
Der Sensor gibt das Umgebungslicht in Lux zurück, sobald ein neuer Messwert verfügbar ist. Anhand einer Wertetabelle für typische Lichtverhältnisse habe ich eine sehr einfache Formel entwickelt, um den Lux-Wert in einen Wert zwischen 0 und 1 umzuwandeln, den ich programmatisch der Variablen --opacity zuweise.
const luxToOpacity = (lux) => {
if (lux > 250) {
return 1;
}
return lux / 250;
};
const sensor = new window.AmbientLightSensor();
sensor.onreading = () => {
console.log('Current light level:', sensor.illuminance);
document.documentElement.style.setProperty(
'--opacity',
luxToOpacity(sensor.illuminance),
);
};
sensor.onerror = (event) => {
console.log(event.error.name, event.error.message);
};
(async () => {
const {state} = await navigator.permissions.query({
name: 'ambient-light-sensor',
});
if (state === 'granted') {
sensor.start();
}
})();
Im Video unten sehen Sie, wie der Taschenrechner funktioniert, sobald ich das Raumlicht ausreichend aufdrehe. Und da haben Sie ihn: einen skeuomorphen Solarrechner, der tatsächlich funktioniert. Mein guter alter, bewährter TI-30X SOLAR hat einen weiten Weg hinter sich.
Demo
Probieren Sie die Designcember-Rechner-Demo aus und sehen Sie sich den Quellcode auf GitHub an. (Um die App zu installieren, müssen Sie sie in einem eigenen Fenster öffnen. Die eingebettete Version unten löst die Mini-Infoleiste nicht aus.)
Frohen Designcember!