Designcember 계산기

Window Controls Overlay API 및 Ambient Light Sensor API를 사용하여 웹에서 태양 계산기를 다시 만들기 위한 스큐어모픽 시도입니다.

도전과제

1980년대의 아들입니다. 제가 고등학교 때 가장 화제가 되었던 것은 태양계 계산기였습니다. 우리는 모두 학교에서 TI-30X SOLAR을 제공받았습니다. 저는 TI-30X가 처리할 수 있는 가장 높은 숫자인 69의 계승을 계산하여 계산기를 서로 벤치마킹한 기억을 즐깁니다. (속도 변동은 매우 측정 가능했고 아직도 이유를 모릅니다)

약 28년이 지난 지금은 HTML, CSS, 자바스크립트로 계산기를 다시 만드는 것이 재미있는 Designcember 챌린지가 될 것이라고 생각했습니다. 저는 디자이너가 아니기 때문에 처음부터 시작한 것은 아니지만 Sassja CeballosCodePen을 사용했습니다.

왼쪽에는 HTML, CSS, JS 패널이 쌓여 있고 오른쪽에는 계산기 미리보기가 있는 CodePen 뷰

설치 가능하게 만들기

시작이 나쁘지는 않았지만, 완벽한 스큐어모픽을 얻기 위해 더 신나게 플레이하기로 했습니다. 첫 번째 단계는 앱을 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');
      }
    })(),
  );
});

모바일과의 혼합

이제 앱을 설치할 수 있으므로 다음 단계는 앱이 운영체제 앱과 최대한 잘 융합되도록 하는 것입니다. 모바일에서는 웹 앱 매니페스트에서 표시 모드를 fullscreen로 설정하면 됩니다.

{
  "display": "fullscreen"
}

카메라 구멍이나 노치가 있는 기기에서는 콘텐츠가 전체 화면을 덮도록 표시 영역을 조정하여 앱을 멋지게 보이게 만듭니다.

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

Pixel 6 Pro 휴대전화에서 전체 화면이 실행되는 Designcember 계산기

데스크톱과 혼합

데스크톱에서는 Window Controls Overlay라는 멋진 기능을 사용할 수 있습니다. 이 기능을 사용하면 앱 창의 제목 표시줄에 콘텐츠를 표시할 수 있습니다. 첫 번째 단계는 디스플레이 모드 대체 시퀀스를 재정의하여 사용 가능한 경우 먼저 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: no-drag를 가져오는 버튼 외에 (-webkit-)app-region: 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 계산기 디스플레이에 계산기 알파벳이 &#39;Google&#39;로 표시되어 있습니다.

실제로 작동하는 태양 전지

궁극적인 괴짜를 위해서 당연히 태양 전지가 제대로 작동하도록 만들어야 했습니다. 계산기는 조명이 충분한 경우에만 작동합니다. 저는 JavaScript를 통해 제어하는 CSS 변수 --opacity를 통해 화면에 표시되는 숫자의 CSS opacity를 설정하여 이를 모델링했습니다.

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

새 판독값이 있을 때마다 센서는 주변광을 럭스 단위로 반환합니다. 일반적인 조명 상황의 값 표를 바탕으로 럭스 값을 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 태양광은 실제로 많은 발전을 이루었습니다.

데모

Designcember 계산기 데모를 사용해 보고 Glitch의 소스 코드를 확인하세요. (앱을 설치하려면 자체 창에서 앱을 열어야 합니다. 아래에 삽입된 버전은 미니 정보 표시줄을 트리거하지 않습니다.)

즐거운 Designcember 입니다!