Calcolatore Designcember

Un tentativo sorprendente di ricreare un calcolatore solare sul web con l'API Window Controls Overlay e l'API Ambient Light Sensor.

La sfida

Sono un bambino degli anni'80. Una cosa che era di gran moda quando ero alle superiori erano le calcolatrici a energia solare. La scuola ci ha regalato un TI-30X SOLAR e ho teneri ricordi di noi che confrontavamo i nostri calcolatori l'uno con l'altro calcolando il fattoriale di 69, il numero più alto che la TI-30X fosse in grado di gestire. (La varianza della velocità era molto misurabile, non ho ancora idea del perché).

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

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

Rendi l'app installabile

Anche se non è stato un brutto inizio, ho deciso di dargli il massimo per dare il massimo. Il primo passo è stato creare una PWA per poterla installare. Gestisco un modello di PWA di riferimento su Glitch che remixo ogni volta che mi serve una rapida demo. Il suo service worker non ti vincerà alcun premio per la programmazione e sicuramente non è pronto per la produzione, ma è sufficiente attivare la barra di informazioni mini 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');
      }
    })(),
  );
});

Integrazione con i dispositivi mobili

Ora che l'app è installabile, il passaggio successivo è integrarla il più possibile con le app del sistema operativo. Sui dispositivi mobili, posso farlo impostando la modalità di visualizzazione su fullscreen nel manifest dell'app web.

{
  "display": "fullscreen"
}

Sui dispositivi dotati di foro o tacca per la fotocamera, ritoccare l'area visibile in modo che i contenuti coprano l'intero schermo conferiscono all'app un aspetto fantastico.

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

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

Integrazione con il desktop

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

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

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

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

Devo poi decidere quali elementi rendere trascinabili, dato che la barra del titolo che generalmente userei per il trascinamento non è disponibile. Nello stile di un widget classico, posso persino trascinare l'intera calcolatrice applicando (-webkit-)app-region: drag, a parte i pulsanti, che ricevo (-webkit-)app-region: no-drag in modo che non possano essere trascinati.

#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 all'overlay dei controlli delle finestre. Con un reale approccio di miglioramento progressivo, carico il codice per questa funzionalità solo quando il browser la supporta.

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

Ogni volta che la geometria dei controlli delle finestre viene sovrapposta, modifico l'app per renderla il più naturale possibile. È consigliabile eliminare il rimbalzo di questo evento, poiché può essere attivato di frequente quando l'utente ridimensiona la finestra. In particolare, applico la classe wco ad alcuni elementi, quindi il CSS di cui sopra si attiva e modifico anche il colore del tema. Posso rilevare se l'overlay dei controlli delle finestre è 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 che è tutto a posto, ho un widget della calcolatrice che assomiglia quasi al classico Winamp, con uno dei temi di Winamp vecchio stile. Ora posso posizionare liberamente la calcolatrice sul desktop e attivare la funzionalità dei controlli delle finestre facendo clic sullo chevron nell'angolo in alto a destra.

Calcolatrice Designcember in esecuzione in modalità autonoma con la funzione Sovrapposizione controlli finestra attiva. Sul display è presente la scritta &quot;Google&quot; nell&#39;alfabeto della calcolatrice.

Un pannello solare effettivamente funzionante

Per uno smanettone impareggiabile, ovviamente dovevo far funzionare effettivamente il pannello solare. La calcolatrice dovrebbe funzionare solo se c'è abbastanza luce. Ho creato questo modello per impostare il opacity CSS delle cifre sul display tramite una variabile CSS --opacity che controllo tramite JavaScript.

:root {
  --opacity: 0.75;
}

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

Utilizzo l'API AmbientLightSensor per rilevare se è disponibile una quantità di luce sufficiente per il funzionamento del calcolatore. Affinché questa API sia disponibile, devo impostare il flag #enable-generic-sensor-extra-classes in about:flags e richiedere l'autorizzazione 'ambient-light-sensor'. Come prima, uso il miglioramento progressivo per caricare il codice pertinente solo quando l'API è supportata.

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

Il sensore restituisce la luce ambientale in unità lux ogni volta che è disponibile una nuova lettura. In base a una tabella di valori di situazioni di luce tipiche, ho trovato una formula molto semplice per convertire il valore lux in un valore compreso tra 0 e 1 che assegno in modo programmatico 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 funziona la calcolatrice una volta che la stanza si illumina abbastanza. Ed ecco qua: un calcolatore a energia solare che funziona davvero. Il mio vecchio TI-30X SOLAR ha fatto molta strada.

Demo

Assicurati di giocare con la demo della Calcolatrice Designcember e di consultare il codice sorgente su Glitch. Per installare l'app, devi aprirla in una finestra separata. La versione incorporata di seguito non attiverà la barra delle informazioni mini.)

Buon Designcember!