Window Controls Overlay API 및 Ambient Light Sensor API를 사용하여 웹에서 태양 계산기를 재현하려는 스큐어모픽 시도입니다.
도전과제
저는 1980년대 아입니다. 제가 고등학교에 다닐 때 유행했던 것이 태양광 계산기였습니다. 학교에서 모두 TI-30X SOLAR를 지급받았는데, TI-30X가 처리할 수 있는 가장 큰 숫자인 69의 배수를 계산하여 계산기를 서로 벤치마킹했던 추억이 있습니다. 속도 변동은 매우 측정 가능했으며 그 이유를 아직 모르겠습니다.
이제 거의 28년이 지난 지금, HTML, CSS, JavaScript로 계산기를 다시 만드는 것이 Designcember 챌린지로 재미있을 것 같았습니다. 저는 디자이너가 아니므로 처음부터 시작하지 않고 Sassja Ceballos의 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" />
데스크톱과 조화
데스크톱에는 앱 창의 제목 표시줄에 콘텐츠를 배치할 수 있는 멋진 기능인 창 컨트롤 오버레이가 있습니다. 첫 번째 단계는 디스플레이 모드 대체 시퀀스를 재정의하여 사용 가능한 경우 먼저 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와 거의 비슷한 느낌의 계산기 위젯을 사용할 수 있습니다. 이제 데스크톱에 계산기를 자유롭게 배치하고 오른쪽 상단의 화살표를 클릭하여 창 컨트롤 기능을 활성화할 수 있습니다.
실제로 작동하는 태양 전지
물론 궁극적인 괴짜가 되기 위해서는 태양 전지를 실제로 작동시켜야 했습니다. 계산기는 충분한 빛이 있을 때만 작동해야 합니다. 이를 모델링한 방법은 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 계산기 데모를 사용해 보고 Glitch의 소스 코드를 확인하세요. 앱을 설치하려면 자체 창에서 열어야 합니다. 아래의 삽입된 버전은 미니 정보 표시줄을 트리거하지 않습니다.)
Designcember(디자인월)을 맞이하여 행복한 하루 보내세요.