Calcolatore Designcember

Un tentativo di ricreare una calcolatrice solare sul web con l'API Window Controls Overlay e l'API Ambient Light Sensor.

La sfida

Sono un bambino degli anni'80. Quando andavo al liceo, andavano di moda le calcolatrici solari. La scuola ci ha regalato a tutti un TI-30X SOLAR, e ho bei ricordi di noi che facevamo il benchmarking delle nostre calcolatrici calcolando il fatoriale di 69, il numero più alto che la TI-30X poteva gestire. (La varianza di velocità era molto misurabile, non ho ancora idea del perché.)

Ora, quasi 28 anni dopo, ho pensato che sarebbe stata una sfida divertente di Designcember ricreare la calcolatrice in HTML, CSS e JavaScript. Non essendo un designer, non ho iniziato da zero, ma con un CodePen di Sassja Ceballos.

Visualizzazione di CodePen con riquadri HTML, CSS e JS sovrapposti a sinistra e l'anteprima della calcolatrice a destra.

Rendilo installabile

Anche se non è stato un cattivo inizio, ho deciso di fare di più per ottenere un'esperienza skeuomorfica completa. Il primo passaggio è stato trasformarla in una PWA in modo che potesse essere installata. Gestisco un modello PWA di riferimento su Glitch che remixo ogni volta che ho bisogno di una demo rapida. Il suo worker di servizio non ti farà vincere alcun premio di programmazione e non è sicuramente pronto per la produzione, ma è sufficiente per attivare la mini barra delle informazioni di Chromium in modo che l'app possa essere installata.

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');
      }
    })(),
  );
});

Fusione con il mobile

Ora che l'app è installabile, il passaggio successivo consiste nel farla integrare il più possibile con le app del sistema operativo. Sul dispositivo mobile, posso farlo impostando la modalità di visualizzazione su fullscreen nel file manifest dell'app web.

{
  "display": "fullscreen"
}

Sui dispositivi con un foro o un notch per la fotocamera, modificare il viewport in modo che i contenuti coprano l'intero schermo rende l'app bellissima.

<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />

Designcember Calculator in esecuzione a schermo intero su uno smartphone Pixel 6 Pro.

Fusione con il computer

Sul computer, posso utilizzare una funzionalità interessante: Overlay dei controlli della finestra, che mi consente di inserire contenuti nella barra del titolo della finestra dell'app. Il primo passaggio consiste nell'override della sequenza di riserva della modalità di visualizzazione in modo che provi prima a utilizzare window-controls-overlay, se disponibile.

{
  "display_override": ["window-controls-overlay"]
}

In questo modo, la barra del titolo scompare e i contenuti si spostano verso l'alto nell'area della barra del titolo come se non fosse presente. La mia idea è spostare la cella solare skeuomorfica nella barra del titolo e il resto dell'interfaccia utente della calcolatrice verso il basso di conseguenza, cosa che posso fare con un po' di CSS che utilizza le variabili di ambiente titlebar-area-*. Noterai che tutti i selettori hanno un wco class, che sarà pertinente un paio di paragrafi più avanti.

#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);
}

Successivamente, devo decidere quali elementi rendere trascinabili, poiché la barra del titolo che di solito userei per il trascinamento non è disponibile. Nello stile di un widget classico, posso anche rendere trascinabile l'intero calcolatore applicando (-webkit-)app-region: drag, a parte i pulsanti, che ricevono (-webkit-)app-region: no-drag in modo che non possano essere utilizzati per trascinare.

#calc_inside.wco,
#calc_solar_cell.wco {
  -webkit-app-region: drag;
  app-region: drag;
}

button {
  -webkit-app-region: no-drag;
  app-region: no-drag;
}

Il passaggio finale consiste nel rendere l'app reattiva alle modifiche dell'overlay dei controlli della finestra. In un approccio di miglioramento progressivo, carico il codice di questa funzionalità solo se il browser la supporta.

if ('windowControlsOverlay' in navigator) {
  import('/wco.js');
}

Ogni volta che la geometria dell'overlay dei controlli della finestra cambia, modifico l'app per renderla il più naturale possibile. È buona norma applicare il debounce a questo evento, poiché può essere attivato frequentemente quando l'utente ridimensiona la finestra. Nello specifico, applico la classe wco ad alcuni elementi, in modo che venga applicato il mio CSS riportato sopra e modifichi anche il colore del tema. Posso rilevare se l'overlay dei controlli della finestra è visibile controllando la proprietà navigator.windowControlsOverlay.visible.

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();

Ora, con tutto questo, ho un widget calcolatrice che sembra quasi il classico Winamp con uno dei temi Winamp old school. Ora posso posizionare la calcolatrice liberamente sul mio computer e attivare la funzionalità di controllo della finestra facendo clic sulla freccia nell'angolo in alto a destra.

Designcember Calculator in esecuzione in modalità autonoma con la funzionalità di overlay dei controlli della finestra attiva. Sul display viene visualizzata la parola &quot;Google&quot; nell&#39;alfabeto della calcolatrice.

Un pannello solare funzionante

Per essere un vero geek, ovviamente dovevo far funzionare la cella solare. La calcolatrice dovrebbe funzionare solo se c'è abbastanza luce. Ho modellato questo aspetto impostando il CSS opacity delle cifre sul display tramite una variabile CSS --opacity che controllo tramite JavaScript.

:root {
  --opacity: 0.75;
}

#calc_expression,
#calc_result {
  opacity: var(--opacity);
}

Per rilevare se è disponibile luce sufficiente per il funzionamento della calcolatrice, utilizzo l'API AmbientLightSensor. Affinché questa API fosse disponibile, ho dovuto impostare il flag #enable-generic-sensor-extra-classes in about:flags e richiedere l'autorizzazione 'ambient-light-sensor'. Come in precedenza, utilizzo il miglioramento progressivo per caricare solo il codice pertinente quando l'API è supportata.

if ('AmbientLightSensor' in window) {
  import('/als.js');
}

Il sensore restituisce la luce ambientale in unità di lux ogni volta che è disponibile una nuova lettura. In base a una tabella di valori di situazioni di illuminazione tipiche, ho trovato una formula molto semplice per convertire il valore in lux in un valore compreso tra 0 e 1 che assegni programmaticamente alla variabile --opacity.

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();
  }
})();

Nel video qui sotto puoi vedere come la calcolatrice inizia a funzionare quando illumino sufficientemente la stanza. Ecco fatto: un calcolatore solare skeuomorfico che funziona davvero. La mia vecchia calcolatrice TI-30X SOLAR, che ha superato la prova del tempo, ha fatto molta strada.

Demo

Assicurati di provare la demo del calcolatore di Designcember e di dare un'occhiata al codice sorgente su Glitch. Per installarla, devi aprirla in una finestra separata. La versione incorporata di seguito non attiverà la mini barra delle informazioni.

Buon Designcember!