Kalkulator Designcember

Skeuomorficzna próba odtworzenia kalkulatora słonecznego w internecie za pomocą interfejsów Window Controls Overlay API i Ambient Light Sensor API.

Wyzwanie

Jestem dzieckiem lat 80. Kiedy chodziłem do szkoły średniej, hitem były kalkulatory słoneczne. Wszyscy dostaliśmy od szkoły kalkulator TI-30X SOLAR. Miło wspominam, jak porównywaliśmy nasze kalkulatory, obliczając silnię liczby 69, czyli najwyższej liczby, jaką TI-30X mógł obsłużyć. (Różnica w szybkości była bardzo zauważalna, ale nadal nie wiem, dlaczego).

Teraz, prawie 28 lat później, pomyślałem, że odtworzenie kalkulatora w językach HTML, CSS i JavaScript będzie ciekawym wyzwaniem w ramach Designcember. Nie jestem projektantem, więc nie zaczynałem od zera, ale od CodePen autorstwa Sassji Ceballos.

Widok CodePen z panelami HTML, CSS i JS po lewej stronie i podglądem kalkulatora po prawej stronie.

Zapewnij możliwość instalacji

To nie był zły początek, ale postanowiłem go ulepszyć, aby uzyskać pełny efekt skeumorfizmu. Pierwszym krokiem było przekształcenie jej w PWA, aby można było ją zainstalować.

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

Połączenie z urządzeniami mobilnymi

Teraz, gdy aplikację można zainstalować, następnym krokiem jest jak najlepsze zintegrowanie jej z aplikacjami systemu operacyjnego. Na urządzeniach mobilnych mogę to zrobić, ustawiając tryb wyświetlania na fullscreen w pliku manifestu aplikacji internetowej.

{
  "display": "fullscreen"
}

Na urządzeniach z otworem lub wycięciem na aparat dostosowanie obszaru wyświetlania tak, aby treść pokrywała cały ekran, sprawia, że aplikacja wygląda świetnie.

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

Kalkulator Designcember działający na pełnym ekranie na telefonie Pixel 6 Pro.

Mieszanie z pulpitem

Na komputerze mogę użyć fajnej funkcji: nakładki elementów sterujących okna, która pozwala umieścić treść na pasku tytułu okna aplikacji. Pierwszym krokiem jest zastąpienie sekwencji zastępczej trybu wyświetlania, aby w pierwszej kolejności próbowała użyć window-controls-overlay, gdy jest dostępny.

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

Dzięki temu pasek tytułu znika, a treść przesuwa się w górę do obszaru paska tytułu, tak jakby go nie było. Mój pomysł polega na przeniesieniu skeumorficznego ogniwa słonecznego na pasek tytułu i odpowiednim przesunięciu reszty interfejsu kalkulatora w dół. Mogę to zrobić za pomocą kodu CSS, który używa zmiennych środowiskowych titlebar-area-*. Zauważysz, że wszystkie selektory mają klasę wco, która będzie istotna w dalszej części artykułu.

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

Następnie muszę zdecydować, które elementy mają być przeciągane, ponieważ pasek tytułu, którego zwykle używam do przeciągania, jest niedostępny. W stylu klasycznego widżetu mogę nawet sprawić, że cały kalkulator będzie można przeciągać, stosując (-webkit-)app-region: drag, z wyjątkiem przycisków, które mają (-webkit-)app-region: no-drag, więc nie można ich używać do przeciągania.

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

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

Ostatnim krokiem jest sprawienie, aby aplikacja reagowała na zmiany nakładki elementów sterujących oknem. W prawdziwym podejściu opartym na progresywnym ulepszaniu ładuję kod tej funkcji tylko wtedy, gdy przeglądarka ją obsługuje.

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

Za każdym razem, gdy zmienia się geometria nakładki elementów sterujących oknem, modyfikuję aplikację, aby wyglądała jak najbardziej naturalnie. Warto zastosować debounce w przypadku tego zdarzenia, ponieważ może ono być wywoływane często, gdy użytkownik zmienia rozmiar okna. Mianowicie stosuję klasę wco do niektórych elementów, dzięki czemu mój kod CSS zaczyna działać, a ja zmieniam kolor motywu. Mogę wykryć, czy nakładka elementów sterujących okna jest widoczna, sprawdzając właściwość 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();

Teraz mam widżet kalkulatora, który wygląda prawie jak klasyczny Winamp z jednym ze starych motywów. Teraz mogę swobodnie umieścić kalkulator na pulpicie i aktywować funkcję sterowania oknem, klikając strzałkę w prawym górnym rogu.

Kalkulator Designcember działający w trybie samodzielnym z aktywną funkcją nakładki elementów sterujących okna. Na wyświetlaczu pojawia się napis „Google” w alfabecie kalkulatora.

Działające ogniwo słoneczne

Aby uzyskać najlepszy efekt, musiałem oczywiście sprawić, żeby ogniwo słoneczne naprawdę działało. Kalkulator powinien działać tylko wtedy, gdy jest wystarczająco dużo światła. Sposób, w jaki to zamodelowałem, polega na ustawieniu właściwości CSS opacity cyfr na wyświetlaczu za pomocą zmiennej CSS --opacity, którą kontroluję za pomocą JavaScriptu.

:root {
  --opacity: 0.75;
}

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

Aby sprawdzić, czy jest wystarczająco dużo światła, aby kalkulator działał, używam interfejsu API AmbientLightSensor. Aby ten interfejs API był dostępny, musiałem ustawić flagę #enable-generic-sensor-extra-classes w about:flags i poprosić o uprawnienie 'ambient-light-sensor'. Podobnie jak wcześniej, używam progresywnego ulepszania, aby wczytywać odpowiedni kod tylko wtedy, gdy interfejs API jest obsługiwany.

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

Czujnik zwraca jasność otoczenia w luksach, gdy tylko dostępny jest nowy odczyt. Na podstawie tabeli wartości typowych sytuacji oświetleniowych opracowałem bardzo prostą formułę, która pozwala przekonwertować wartość luksów na wartość z zakresu od 0 do 1. Przypisuję ją programowo do zmiennej --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();
  }
})();

Na filmie poniżej widać, jak kalkulator zaczyna działać, gdy włączam wystarczająco jasne światło w pokoju. I oto on: skeumorficzny kalkulator solarny, który naprawdę działa. Mój stary, sprawdzony TI-30X SOLAR przeszedł długą drogę.

Prezentacja

Koniecznie wypróbuj wersję demonstracyjną kalkulatora Designcember i sprawdź kod źródłowy na GitHubie. (Aby zainstalować aplikację, musisz otworzyć ją w osobnym oknie. Wersja umieszczona poniżej nie spowoduje wyświetlenia minipaska informacyjnego).

Wesołego Designcember!