Calculatrice de conception

Une tentative skeuomorphe de recréer une calculatrice solaire sur le Web avec l'API Window Controls Overlay et l'API Ambient Light Sensor.

Problématique

Je suis une petite fille des années 80. Quand j'étais au lycée, les calculatrices solaires sont une chose qui faisait vraiment fureur. L'école nous a tous remis un modèle TI-30X SOLAR, et j'ai beaucoup de souvenirs de nous avoir comparés entre eux en calculant la factorielle de 69, le nombre maximal que la TI-30X pouvait gérer. (La variance de la vitesse était très mesurable, je ne sais toujours pas pourquoi.)

Presque 28 ans plus tard, je pensais qu'il serait intéressant pour Designcember de recréer la calculatrice en HTML, CSS et JavaScript. N'étant pas vraiment conceptrice, je n'ai pas commencé de zéro, mais avec un CodePen de Sassja Ceballos.

Vue CodePen avec panneaux HTML, CSS et JS empilés à gauche, et aperçu de la calculatrice à droite

Permettre l'installation

Même si ce n'était pas un mauvais départ, j'ai décidé d'y mettre le feu au skeuomorphisme. La première étape consistait à en faire une PWA afin de pouvoir l'installer. Je gère un modèle de PWA de base sur Glitch que je remixe chaque fois que j'ai besoin d'une démonstration rapide. Son service worker ne vous remportera aucun prix de codage et il n'est pas prêt pour la production, mais il suffit de déclencher la mini-barre d'informations de Chromium pour que l'application puisse être installée.

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

Combiner avec les mobiles

Maintenant que l'application est installable, l'étape suivante consiste à la faire se fondre dans la mesure du possible avec les applications du système d'exploitation. Pour ce faire, sur mobile, définissez le mode d'affichage sur fullscreen dans le fichier manifeste de l'application Web.

{
  "display": "fullscreen"
}

Sur les appareils dotés d'un trou de caméra ou d'une encoche, modifiez la fenêtre d'affichage pour que le contenu couvre tout l'écran, ce qui rend l'application magnifique.

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

Calculatrice Designcember en plein écran sur un téléphone Pixel 6 Pro.

Mixte avec les ordinateurs

Sur ordinateur, il existe une fonctionnalité intéressante que je peux utiliser : Window Controls Overlay (Superposition des commandes de fenêtre), qui me permet de placer du contenu dans la barre de titre de la fenêtre de l'application. La première étape consiste à ignorer la séquence de remplacement du mode d'affichage afin d'essayer d'utiliser window-controls-overlay en premier lorsqu'il est disponible.

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

Ainsi, la barre de titre disparaît efficacement et le contenu s'affiche dans la zone de la barre de titre comme si elle n'était pas là. Mon idée est de déplacer la cellule solaire skeuomorphique vers le haut dans la barre de titre et de déplacer le reste de l'interface utilisateur de la calculatrice vers le bas en conséquence, ce que je peux faire avec certains CSS qui utilisent les variables d'environnement titlebar-area-*. Vous remarquerez que tous les sélecteurs contiennent une classe wco, qui sera pertinente quelques paragraphes plus bas.

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

Ensuite, je dois décider des éléments à rendre déplaçables, car la barre de titre que j'utilise habituellement pour faire glisser n'est pas disponible. Dans le style d'un widget classique, je peux même rendre l'ensemble de la calculatrice déplaçable en appliquant (-webkit-)app-region: drag, à l'exception des boutons, qui obtiennent (-webkit-)app-region: no-drag et ne peuvent donc pas être utilisés pour faire glisser.

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

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

La dernière étape consiste à rendre l'application réactive aux changements de superposition des commandes de fenêtre. Dans une véritable approche d'amélioration progressive, je ne charge le code de cette fonctionnalité que lorsque le navigateur la prend en charge.

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

Chaque fois que la fenêtre du contrôle de la superposition change de forme, je modifie l'application pour la rendre aussi naturelle que possible. Nous vous recommandons de refuser cet événement, car il peut être déclenché fréquemment lorsque l'utilisateur redimensionne la fenêtre. En d'autres termes, j'applique la classe wco à certains éléments pour que le code CSS ci-dessus s'active et je modifie également la couleur du thème. Pour détecter si la superposition des commandes de fenêtre est visible, vérifiez la propriété 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();

Maintenant que tout cela est en place, j'obtiens un widget de calculatrice qui ressemble presque au Winamp classique, avec l'un des thèmes Winamp anciens. Je peux maintenant placer librement la calculatrice sur mon bureau et activer les commandes de fenêtre en cliquant sur le chevron en haut à droite.

Calculatrice Designcember s&#39;exécutant en mode autonome avec la fonctionnalité &quot;Superposition des commandes de fenêtre&quot; active L&#39;affichage indique &quot;Google&quot; dans l&#39;alphabet de la calculatrice.

Une cellule solaire en état de marche

Il fallait bien sûr que la cellule photovoltaïque fonctionne réellement. La calculatrice ne devrait fonctionner que si la lumière est suffisante. Pour modéliser cela, j'ai défini le opacity CSS des chiffres affichés à l'écran via une variable CSS --opacity que je contrôle via JavaScript.

:root {
  --opacity: 0.75;
}

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

Pour déterminer si la lumière disponible est suffisante pour que la calculatrice fonctionne, j'utilise l'API AmbientLightSensor. Pour que cette API soit disponible, j'ai dû définir l'option #enable-generic-sensor-extra-classes dans about:flags et demander l'autorisation 'ambient-light-sensor'. Comme précédemment, j'utilise l'amélioration progressive pour ne charger le code approprié que lorsque l'API est compatible.

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

Le capteur renvoie la lumière ambiante en lux chaque fois qu'une nouvelle mesure est disponible. Sur la base d'un tableau de valeurs de situations d'éclairage typiques, j'ai élaboré une formule très simple pour convertir la valeur de lux en une valeur comprise entre 0 et 1, que j'attribue de manière programmatique à la variable --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();
  }
})();

Dans la vidéo ci-dessous, vous pouvez voir comment la calculatrice démarre une fois que j'éclaire suffisamment la pièce. Et voilà: un calculateur solaire skeuomorphique qui fonctionne vraiment. La TI-30X SOLAR, qui a été testée, a beaucoup progressé.

Démonstration

Assurez-vous de jouer à la démo de Designcember Calculator et de consulter le code source sur Glitch. (Pour installer l'application, vous devez l'ouvrir dans sa propre fenêtre. La version intégrée ci-dessous ne déclenchera pas la mini-barre d'informations.)

Joyeux Designcember !