Uma tentativa esqueomórfica de recriar uma calculadora solar na Web com a API Window Controls Overlay e a API Ambient Light Sensor.
O desafio
Sou criança dos anos 1980. Uma coisa que foi toda a raiva quando eu estava no ensino médio são as calculadoras solares. Todos nós recebemos um TI-30X SOLAR da escola, e eu tenho boas memórias de quando comparamos nossas calculadoras calculando o fator 69, o número mais alto que o TI-30X consegue processar. (A variação de velocidade foi muito mensurável, ainda não tenho ideia do motivo.)
Agora, quase 28 anos depois, pensei que seria um desafio divertido da DesignCember recriar a calculadora em HTML, CSS e JavaScript. Não sendo um designer muito útil, não comecei do zero, mas com um CodePen de Sassja Ceballos (links em inglês).
Torne-o instalável
Embora não tenha sido um mau começo, decidi aumentar o ritmo para ter uma incrível incrível imaginação esquemórfica. A primeira etapa foi torná-lo um PWA para que pudesse ser instalado. Eu mantenho um modelo de PWA de referência no Glitch que eu remixo sempre que preciso de uma demonstração rápida. O service worker não vai ganhar nenhum prêmio de programação e definitivamente não está pronto para produção, mas é suficiente para acionar a minibarra de informações do Chromium para que o app possa ser instalado.
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');
}
})(),
);
});
Combinação com dispositivos móveis
Agora que o app pode ser instalado, a próxima etapa é fazer com que ele se misture o máximo possível aos apps do sistema operacional. Em
dispositivos móveis, posso fazer isso definindo o modo de exibição como fullscreen
no manifesto do app da Web.
{
"display": "fullscreen"
}
Em dispositivos com um furo ou entalhe para a câmera, ajustar a janela de visualização para que o conteúdo cubra toda a tela deixa o app bonito.
<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />
Mesclagem com o computador
No computador, há um recurso legal que posso usar:
a sobreposição dos controles de janela, que permite colocar conteúdo na barra de título
da janela do app. A primeira etapa é substituir a sequência de substituição do modo de exibição para que ela tente
usar window-controls-overlay
primeiro quando estiver disponível.
{
"display_override": ["window-controls-overlay"]
}
Isso faz com que a barra de título desapareça e o conteúdo se mova para a área da barra de título como se
ela não estivesse lá. Minha ideia é mover a célula solar esqueomórfica para cima na barra de título e o restante da interface da calculadora para baixo, o que posso fazer com alguns CSS que usam as variáveis de ambiente titlebar-area-*
. Você vai notar que todos os seletores têm uma classe wco
, que será relevante alguns parágrafos depois.
#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);
}
Em seguida, preciso decidir quais elementos devo tornar arrastáveis, já que a barra de título que eu normalmente
usaria para arrastar não está disponível. No estilo de um widget clássico, posso até mesmo tornar
toda a calculadora arrastável aplicando (-webkit-)app-region: drag
, exceto os botões, que
recebem (-webkit-)app-region: no-drag
para que não possam ser usados para arrastar.
#calc_inside.wco,
#calc_solar_cell.wco {
-webkit-app-region: drag;
app-region: drag;
}
button {
-webkit-app-region: no-drag;
app-region: no-drag;
}
A etapa final é fazer com que o app reative as mudanças na sobreposição dos controles da janela. Em uma abordagem de aprimoramento progressivo real, carrego o código desse recurso apenas quando o navegador oferece suporte a ele.
if ('windowControlsOverlay' in navigator) {
import('/wco.js');
}
Sempre que a janela controla a geometria da sobreposição, modifico o app para que ele pareça o mais natural possível. É uma boa ideia retardar esse evento, já que ele pode ser acionado com frequência quando o usuário redimensiona a janela. Ou seja, eu aplico a classe wco
a alguns elementos para que
o CSS acima seja iniciado e também mudo a cor do tema. Posso detectar se a sobreposição de controles
da janela está visível verificando a propriedade 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();
Agora com tudo isso definido, tenho um widget de calculadora que parece com o Winamp clássico, com um dos antigos temas Winamp. Agora posso colocar a calculadora livremente na minha área de trabalho e ativar o recurso de controles da janela clicando na divisa no canto superior direito.
Uma célula solar em funcionamento
Sei que é preciso fazer a célula solar funcionar de verdade. A calculadora
só vai funcionar se houver luz suficiente. Modelei isso definindo o CSS opacity
dos dígitos na tela por uma variável CSS --opacity
que eu controlo via JavaScript.
:root {
--opacity: 0.75;
}
#calc_expression,
#calc_result {
opacity: var(--opacity);
}
Para detectar se há luz suficiente para a calculadora funcionar, uso a
API AmbientLightSensor
. Para
que essa API estivesse disponível, eu precisava definir a flag #enable-generic-sensor-extra-classes
em
about:flags
e solicitar a permissão 'ambient-light-sensor'
. Como antes, eu uso o aprimoramento
progressivo para carregar apenas o código relevante quando houver suporte para a API.
if ('AmbientLightSensor' in window) {
import('/als.js');
}
O sensor retorna a luz ambiente em unidades de lux sempre que uma
nova leitura está disponível. Com base em uma
tabela de valores de situações de iluminação típicas, criei
uma fórmula muito simples para converter o valor de lux em um valor entre 0 e 1 que
atribuo de maneira programática à variável --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();
}
})();
No vídeo abaixo, você pode ver como a calculadora começa a funcionar depois que aceno a luz da sala o suficiente. E aí está: uma calculadora solar esqueomórfica que de fato funciona. Meu bom e velho TI-30X SOLAR já percorreu um longo caminho, de fato.
Demonstração
Teste a demonstração da Calculadora da Designcember e confira o código-fonte no Glitch. Para instalar o app, abra-o em uma janela própria. a versão incorporada abaixo não acionará a minibarra de informações.
Feliz Designcember!