창 컨트롤 오버레이 API 및 주변광 센서 API를 사용하여 웹에서 태양광 계산기를 재현하려는 스큐어모픽 시도입니다.
과제
저는 1980년대에 태어났습니다. 제가 고등학교에 다닐 때 유행했던 것은 태양광 계산기였습니다. 학교에서 모두에게 TI-30X SOLAR를 주었는데, TI-30X가 처리할 수 있는 가장 큰 숫자인 69의 계승을 계산하여 서로의 계산기를 벤치마킹했던 기억이 납니다. (속도 차이는 매우 측정 가능했지만, 그 이유는 아직 알 수 없습니다.)
이제 거의 28년이 지난 지금, HTML, CSS, JavaScript로 계산기를 다시 만드는 것이 재미있는 Designcember 챌린지가 될 것이라고 생각했습니다. 디자이너가 아니기 때문에 처음부터 시작하지 않고 Sassja Ceballos의 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" />

데스크톱과 조화롭게 어우러짐
데스크톱에서는 창 컨트롤 오버레이라는 멋진 기능을 사용할 수 있습니다. 이 기능을 사용하면 앱 창의 제목 표시줄에 콘텐츠를 넣을 수 있습니다. 첫 번째 단계는 디스플레이 모드 대체 시퀀스를 재정의하여 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 테마 중 하나를 사용합니다. 이제 계산기를 데스크톱에 자유롭게 배치하고 오른쪽 상단의 펼침 아이콘을 클릭하여 창 컨트롤 기능을 활성화할 수 있습니다.

실제로 작동하는 태양 전지
궁극의 괴짜가 되기 위해 저는 당연히 태양 전지를 실제로 작동시켜야 했습니다. 계산기는 빛이 충분한 경우에만 작동해야 합니다. 이것을 모델링한 방법은 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 보내세요.