尝试使用 Window Controls Overlay API 和 Ambient Light Sensor API 在 Web 上以拟物化方式重新创建太阳能计算器。
挑战
我是 20 世纪 80 年代的孩子。在我上高中时,太阳能计算器风靡一时。学校给我们每人发了一个 TI-30X SOLAR,我至今还记得我们当时互相比较计算器的性能,计算 69 的阶乘,这是 TI-30X 能处理的最大数字。(速度差异非常明显,但我仍然不知道原因。)
现在,将近 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');
}
})(),
);
});
与移动设备融合
现在,应用已可安装,下一步是尽可能使其与操作系统应用融为一体。在移动设备上,我可以在 Web 应用清单中将显示模式设置为 fullscreen
来实现此目的。
{
"display": "fullscreen"
}
在有摄像头孔或刘海屏的设备上,调整视口,使内容覆盖整个屏幕,可让应用看起来非常漂亮。
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />
与桌面设备融合
在桌面设备上,我可以使用一项很棒的功能:窗口控件叠加层,该功能允许我在应用窗口的标题栏中放置内容。第一步是替换显示模式回退序列,以便在 window-controls-overlay
可用时尝试先使用它。
{
"display_override": ["window-controls-overlay"]
}
这样一来,标题栏实际上会消失,内容会向上移动到标题栏区域,就好像标题栏不存在一样。我的想法是将拟物化的太阳能电池移到标题栏中,并相应地将计算器界面的其余部分向下移动,这可以通过使用 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);
}
接下来,我需要决定要使哪些元素可拖动,因为通常用于拖动的标题栏不可用。按照经典 widget 的样式,我可以应用 (-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();
现在,一切就绪,我获得了一个计算器 widget,感觉几乎就像经典的 Winamp 搭配了旧式 Winamp 主题。现在,我可以随意将计算器放置在桌面上,并通过点击右上角的箭头来激活窗口控制功能。
实际可用的太阳能电池
为了追求极致的极客精神,我当然需要让太阳能电池板实际发挥作用。只有在光线充足的情况下,计算器才能正常运行。我通过 CSS 变量 --opacity
(通过 JavaScript 控制)设置显示屏上数字的 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 快乐!