Ein skeumorphes Beispiel für die Neuerstellung eines Solarrechners im Web mit der Window Controls Overlay API und der Ambient Light Sensor API.
Die Herausforderung
Ich bin ein Kind der 1980er-Jahre. Als ich in der Highschool war, waren Solarrechner total angesagt. Wir alle bekamen von der Schule einen TI-30X SOLAR. Ich erinnere mich noch gut daran, wie wir unsere Taschenrechner 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.)
Fast 28 Jahre später dachte ich, es wäre eine lustige Designcember-Herausforderung, den Taschenrechner in HTML, CSS und JavaScript nachzubilden. Da ich kein großer Designer bin, habe ich nicht bei null angefangen, sondern mit einem CodePen von Sassja Ceballos.
Installierbar machen
Das war zwar kein schlechter Anfang, aber ich wollte es noch etwas aufpeppen, um den skeuomorphen Stil voll auszuschöpfen. Der erste Schritt war, sie in eine PWA umzuwandeln, damit sie installiert werden konnte.
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');
}
})(),
);
});
Remixe mit Mobilgeräten erstellen
Nachdem die App installiert werden kann, besteht der nächste Schritt darin, sie so weit wie möglich in die Apps des Betriebssystems zu integrieren. Auf Mobilgeräten kann ich das tun, indem ich den Anzeigemodus im Web-App-Manifest auf fullscreen
festlege.
{
"display": "fullscreen"
}
Auf Geräten mit einem Kameraloch oder einer Notch sieht die App besonders gut aus, wenn Sie den Viewport anpassen, sodass der Inhalt den gesamten Bildschirm abdeckt.
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />
Mit dem Desktop verschmelzen
Auf dem Desktop gibt es eine tolle Funktion, die ich nutzen kann: Einblenden von Fenstersteuerelementen. Damit kann ich Inhalte in die Titelleiste des App-Fensters einfügen. Im ersten Schritt wird die Fallback-Sequenz für den Anzeigemodus überschrieben, sodass zuerst versucht wird, window-controls-overlay
zu verwenden, wenn es verfügbar ist.
{
"display_override": ["window-controls-overlay"]
}
Dadurch wird die Titelleiste effektiv ausgeblendet und die Inhalte werden in den Bereich der Titelleiste verschoben, als wäre sie nicht vorhanden. Meine Idee ist, die skeumorphe Solarzelle in die Titelleiste zu verschieben und den Rest der Taschenrechner-Benutzeroberfläche entsprechend nach unten zu verschieben. Das kann ich mit etwas CSS erreichen, das die Umgebungsvariablen titlebar-area-*
verwendet. Alle Selektoren haben die Klasse wco
, die in einigen Absätzen weiter unten relevant wird.
#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 sich ziehen lassen, 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 Taschenrechner durch Anwenden von (-webkit-)app-region: drag
verschiebbar machen, 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 muss die App auf Änderungen des Overlays für Fenstersteuerelemente reagieren. Bei einem echten Progressive Enhancement-Ansatz lade ich den Code für diese Funktion nur, wenn der Browser sie unterstützt.
if ('windowControlsOverlay' in navigator) {
import('/wco.js');
}
Immer wenn sich die Geometrie der 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 das CSS von oben greift, und ändere auch die Designfarbe. Ich kann erkennen, ob das Overlay mit den Fenstersteuerelementen sichtbar ist, indem ich die navigator.windowControlsOverlay.visible
-Eigenschaft prü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 Taschenrechner-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 rechts oben auf das Chevron klicke.
Eine funktionierende Solarzelle
Für den ultimativen Geek-Faktor musste ich natürlich dafür sorgen, dass die Solarzelle auch funktioniert. Der Taschenrechner 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 für die Funktion des Rechners vorhanden ist, 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 Progressive Enhancement, um nur den relevanten Code 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 typischer Lichtverhältnisse habe ich eine sehr einfache Formel entwickelt, um den Lux-Wert in einen Wert zwischen 0 und 1 umzuwandeln, den ich der Variablen --opacity
programmatisch 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 folgenden Video sehen Sie, wie der Taschenrechner funktioniert, sobald ich das Raumlicht ausreichend aufdrehe. Und das war es auch schon: ein skeumorpher Solarrechner, der tatsächlich funktioniert. Mein guter alter, bewährter TI-30X SOLAR hat schon viel mitgemacht.
Demo
Sehen Sie sich die Designcember Calculator-Demo an und werfen Sie einen Blick auf den Quellcode auf GitHub. Wenn Sie die App installieren möchten, müssen Sie sie in einem eigenen Fenster öffnen. Die eingebettete Version unten löst die Mini-Infoleiste nicht aus.)
Schönen Designcember!