Kalkulator projektanta

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

Wyzwanie

Jestem dzieckiem lat 80. Gdy chodziłem do liceum, bardzo popularne były kalkulatory słoneczne. Wszyscy dostaliśmy od szkoły kalkulatory TI-30X SOLAR. Miło wspominam, jak porównywaliśmy nasze kalkulatory, obliczając silnię liczby 69, czyli największej liczby, jaką mógł obsłużyć TI-30X. (Różnica prędkości była bardzo mierzalna, nadal nie wiem dlaczego).

Po prawie 28 latach postanowiłem odtworzyć kalkulator w HTML-u, CSS-ie i JavaScriptcie w ramach wyzwania Designcember. Nie jestem projektantem, więc nie zaczynałem od zera, ale od CodePen autorstwa Sassji Ceballos.

Widok CodePen z ułożonymi w stos panelami HTML, CSS i JS po lewej stronie oraz podglądem kalkulatora po prawej stronie.

Umożliw instalację

To nie był zły początek, ale postanowiłem go ulepszyć, aby uzyskać pełną skeuomorficzną doskonałość. 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');
      }
    })(),
  );
});

Miksowanie na urządzeniu mobilnym

Teraz, gdy aplikacja jest już gotowa do zainstalowania, następnym krokiem jest jak największe dopasowanie jej do aplikacji systemu operacyjnego. Na urządzeniu mobilnym mogę to zrobić, ustawiając tryb wyświetlania na fullscreen w pliku manifestu aplikacji internetowej.

{
  "display": "fullscreen"
}

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

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

Kalkulator Designcember w trybie pełnoekranowym na telefonie Pixel 6 Pro.

Łączenie z komputerem

Na komputerze mam fajną funkcję: nakładkę z elementami sterującymi okna, która pozwala mi umieszczać treści na pasku tytułu okna aplikacji. Pierwszym krokiem jest zastąpienie sekwencji rezerwowej trybu wyświetlania, aby w przypadku dostępności najpierw używać metody window-controls-overlay.

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

Sprawia to, że pasek tytułu znika, a treść przesuwa się w jego miejsce, jakby go tam nie było. Chcę przenieść skeuomorficzną komórkę słoneczną do paska tytułu, a resztę interfejsu kalkulatora odpowiednio niżej. Mogę to zrobić za pomocą kodu CSS, który korzysta ze zmiennych środowiskowych titlebar-area-*. Zauważysz, że wszystkie selektory mają klasę wco, o której wspomnimy kilka akapitów dalej.

#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, nie jest dostę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 otrzymują (-webkit-)app-region: no-drag, aby nie można było 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 dostosowanie aplikacji do zmian w nakładce z elementami sterującymi okna. W przypadku prawdziwego progresywnego ulepszania kod tej funkcji jest wczytywany tylko wtedy, gdy przeglądarka go obsługuje.

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

Gdy geometria nakładki elementów sterujących okna się zmienia, modyfikuję aplikację, aby wyglądała jak najbardziej naturalnie. Warto ograniczyć częstotliwość wywoływania tego zdarzenia, ponieważ może ono być wywoływane często, gdy użytkownik zmienia rozmiar okna. Do niektórych elementów stosuję klasę wco, więc włącza się powyższy kod CSS, a także zmieniam kolor motywu. Mogę sprawdzić, 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();

Dzięki temu mam widżet kalkulatora, który przypomina klasyczny Winamp z jednym ze starych motywów Winampa. Kalkulator można teraz swobodnie umieszczać 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 oknem. Na wyświetlaczu pojawia się napis „Google” w alfabecie kalkulatora.

działające ogniwo słoneczne,

Aby w pełni wykorzystać potencjał tego gadżetu, musiałem oczywiście sprawić, żeby ogniwo słoneczne działało. Kalkulator powinien działać tylko wtedy, gdy jest wystarczająco dużo światła. Zrobiłem to, ustawiając kolor 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 AmbientLightSensor API. Aby ten interfejs API był dostępny, musiałem ustawić flagę #enable-generic-sensor-extra-classesabout: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 wartość jasności otoczenia w luksach, gdy tylko dostępny jest nowy odczyt. Na podstawie tabeli wartości typowych sytuacji oświetleniowych opracowałem bardzo prosty wzór, który pozwala przekształcić wartość luksa na wartość z zakresu od 0 do 1. Tę wartość przypisuję 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łączę światło w pomieszczeniu. I gotowe: skeuomorficzny kalkulator słoneczny, który naprawdę działa. Mój stary, sprawdzony kalkulator TI-30X SOLAR przeszedł długą drogę.

Prezentacja

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!