設計工具計算機

使用 Window Controls Overlay API 和 Ambient Light Sensor API,嘗試在網路上重建太陽能計算機的擬真版本。

難題

我是 1980 年代的孩子。我高中時很流行太陽能計算機。學校都會提供 TI-30X SOLAR 給我們,而我很懷念那段回憶,我們會比較各自的計算機,計算 69 的階乘,這是 TI-30X 可處理的最高數字。(速度變化非常明顯,但我仍不清楚原因為何)。

如今,時隔將近 28 年,我認為在 Designcember 挑戰中,以 HTML、CSS 和 JavaScript 重建計算機會是個有趣的挑戰。由於我不是設計師,所以並非從頭開始,而是使用 Sassja Ceballos 製作的 CodePen

CodePen 檢視畫面,左側顯示堆疊的 HTML、CSS 和 JS 面板,右側則是計算機的預覽畫面。

讓應用程式可供安裝

雖然不是個壞開始,但我決定加把勁,讓它成為令人驚豔的擬真圖示。第一步是將應用程式設為 PWA,以便安裝。我會在 Glitch 上維護基準 PWA 範本,只要需要快速示範,我就會重混。這個服務工作站不會讓您獲得任何程式設計獎項,而且絕對適合實際發布,但它足以觸發 Chromium 的迷你資訊列,讓應用程式可以安裝。

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

與行動裝置融合

應用程式可供安裝後,下一步就是盡可能讓應用程式與作業系統應用程式融為一體。在行動裝置上,您可以透過在 Web 應用程式資訊清單中將顯示模式設為 fullscreen 來執行此操作。

{
  "display": "fullscreen"
}

在裝置上有相機孔或凹口時,調整檢視區大小,讓內容覆蓋整個螢幕,讓應用程式看起來更美觀。

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

在 Pixel 6 Pro 手機上以全螢幕模式執行 Designcember 計算機。

與電腦版混合

在電腦上,我可以使用一個很酷的功能:視窗控制項重疊顯示,可讓我將內容放入應用程式視窗的標題列中。第一步是覆寫顯示模式備用順序,讓系統在可用時先嘗試使用 window-controls-overlay

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

這樣一來,標題列就會有效消失,內容會向上移動至標題列區域,就好像沒有標題列一樣。我的想法是將擬物太陽能電池往上移至標題列,並將其他計算機 UI 往下移,我可以使用使用 titlebar-area-* 環境變數的 CSS 來執行這項操作。您會發現所有選取器都帶有 wco 類別,這會在幾段落下方出現。

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

接下來,我需要決定要讓哪些元素可拖曳,因為我通常用來拖曳的標題列無法使用。在經典小工具的樣式中,我甚至可以套用 (-webkit-)app-region: drag 讓整個計算機可拖曳,除了按鈕之外,因為按鈕會取得 (-webkit-)app-region: no-drag,因此無法用於拖曳。

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

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

最後一個步驟是讓應用程式回應視窗控制項重疊顯示的變更。在真正的漸進式改善方法中,我只會在瀏覽器支援這項功能時載入這項功能的程式碼。

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

每當視窗控制項疊加幾何圖形發生變更時,我都會修改應用程式,讓畫面看起來盡可能自然。建議您對這項事件進行去抖動,因為使用者調整視窗大小時,這項事件可能會頻繁觸發。也就是說,我將 wco 類別套用至部分元素,因此上述的 CSS 會生效,我也變更主題顏色。我可以檢查 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();

完成所有設定後,我得到的計算機小工具幾乎與經典的 Winamp 相同,並使用其中一個老舊的 Winamp 主題。我現在可以自由地將計算機放在桌面上,並透過點選右上角的箭頭符號啟用視窗控制項功能。

在獨立模式下執行 Designcember 計算機,並啟用「Window Controls Overlay」功能。畫面上會顯示「Google」字樣,使用計算機字母。

實際運作的太陽能電池

為了達到極致的技術宅程度,我當然要讓太陽能電池實際運作。只有在有足夠光線的情況下,計算機才會運作。我模擬這個情況的方式,是透過 CSS 變數 --opacity 設定螢幕上數字的 CSS opacity,並透過 JavaScript 控制這個變數。

:root {
  --opacity: 0.75;
}

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

為了偵測是否有足夠的光線讓計算機運作,我使用 AmbientLightSensor API。為了讓這個 API 可供使用,我必須在 about:flags 中設定 #enable-generic-sensor-extra-classes 標記,並要求 'ambient-light-sensor' 權限。如同先前所述,我使用漸進式增強功能,只在 API 支援時載入相關程式碼。

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

每當有新的讀數時,感應器就會以 lux 單位傳回環境光。根據一般光線環境的值表,我設計了一個非常簡單的公式,將勒克斯值轉換為 0 到 1 之間的值,並以程式設計方式指派給 --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();
  }
})();

在下方影片中,您可以看到我將房間燈光調亮後,計算機如何開始運作。這就是實際運作的擬真太陽能計算機。我那經過時間考驗的 TI-30X SOLAR 確實經歷了一段漫長的旅程。

示範

請務必試用 Designcember 計算機範例,並查看 Glitch 上的原始碼。(如要安裝應用程式,您需要在獨立視窗中開啟應用程式。下方嵌入的版本不會觸發迷你資訊列)。

祝您度過愉快的 Designcember