Designcember 계산기

Window Controls Overlay API와 Ambient Light Sensor API를 사용하여 웹에서 태양열 계산기를 스큐어모픽으로 재현하려는 시도

과제

저는 1980년대에 태어났습니다. 제가 고등학교에 다닐 때 태양광 계산기가 큰 인기를 끌었습니다. 학교에서 모두에게 TI-30X SOLAR를 지급했는데, TI-30X가 처리할 수 있는 가장 큰 숫자인 69의 팩토리얼을 계산하여 서로의 계산기를 벤치마킹했던 기억이 납니다. (속도 차이는 매우 측정 가능했지만 아직 이유를 모르겠습니다.)

거의 28년이 지난 지금, HTML, CSS, JavaScript로 계산기를 다시 만드는 것이 재미있는 Designcember 챌린지가 될 것이라고 생각했습니다. 디자이너가 아닌 저는 처음부터 시작하지 않고 Sassja CeballosCodePen으로 시작했습니다.

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

설치 가능하게 만들기

나쁘지 않은 시작이었지만 완전한 스큐어모피즘을 위해 더 멋지게 만들기로 했습니다. 첫 번째 단계는 설치할 수 있도록 PWA로 만드는 것이었습니다.

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를 사용할 수 있는 경우 먼저 사용하도록 하는 것입니다.

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

센서는 새로운 판독값을 사용할 수 있을 때마다 주변 조명을 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 계산기 데모를 사용해 보고 GitHub의 소스 코드를 확인하세요. (앱을 설치하려면 자체 창에서 앱을 열어야 합니다. 아래 삽입된 버전은 미니 정보 표시줄을 트리거하지 않습니다.)

Designcember를 즐기세요!