Дизайндекабрь Калькулятор

Скевоморфная попытка воссоздать солнечный калькулятор в Интернете с помощью API-интерфейса Window Controls Overlay и API датчика внешней освещенности.

Задача

Я ребенок 1980-х годов. Когда я учился в старшей школе, в моде были солнечные калькуляторы. В школе нам всем подарили TI-30X SOLAR , и у меня остались приятные воспоминания о том, как мы сравнивали наши калькуляторы друг с другом, вычисляя факториал 69 — наибольшее число, с которым мог справиться TI-30X. (Отклонение скорости было очень измеримым, я до сих пор понятия не имею, почему.)

Теперь, почти 28 лет спустя, я подумал, что было бы забавно воссоздать калькулятор в HTML, CSS и JavaScript. Будучи не очень хорошим дизайнером, я начал не с нуля, а с CodePen от Sassja Ceballos .

Представление CodePen со сложенными панелями HTML, CSS и JS слева и предварительным просмотром калькулятора справа.

Сделайте его доступным для установки

Хотя начало было неплохим, я решил прокачать его до полной скевоморфной крутости. Первым шагом было сделать его PWA, чтобы его можно было установить. Я поддерживаю базовый шаблон PWA на Glitch , который я переделываю всякий раз, когда мне нужна быстрая демонстрация. Его сервисный работник не принесет вам никаких наград в области кодирования, и он определенно не готов к работе, но этого достаточно, чтобы вызвать мини-информационную панель 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" />

Калькулятор Designcember работает в полноэкранном режиме на телефоне Pixel 6 Pro.

Смешение с рабочим столом

На рабочем столе есть интересная функция, которую я могу использовать: Window Controls Overlay , которая позволяет мне помещать контент в строку заголовка окна приложения. Первым шагом является переопределение последовательности возврата к режиму отображения, чтобы он сначала пытался использовать window-controls-overlay , когда он доступен.

{
  "display_override": ["window-controls-overlay"]
}

Это приводит к тому, что строка заголовка фактически исчезает, а содержимое перемещается вверх в область строки заголовка, как если бы строки заголовка там не было. Моя идея состоит в том, чтобы переместить скевоморфный солнечный элемент вверх в строку заголовка, а остальную часть пользовательского интерфейса калькулятора соответственно вниз, что я могу сделать с помощью CSS, который использует переменные среды titlebar-area-* . Вы заметите, что все селекторы имеют класс 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 . Теперь я могу свободно разместить калькулятор на своем рабочем столе и активировать функцию управления окном, щелкнув шеврон в правом верхнем углу.

Калькулятор Designcember работает в автономном режиме с активной функцией наложения элементов управления окнами. На дисплее алфавитом калькулятора будет написано «Google».

Реально работающий солнечный элемент

Для полного чудачества мне, конечно, нужно было заставить солнечную батарею действительно работать. Калькулятор должен работать только при достаточном освещении. Я смоделировал это, установив opacity CSS цифр на дисплее с помощью переменной CSS --opacity , которой я управляю через JavaScript.

:root {
  --opacity: 0.75;
}

#calc_expression,
#calc_result {
  opacity: var(--opacity);
}

Чтобы определить, достаточно ли света для работы калькулятора, я использую API AmbientLightSensor . Чтобы этот API был доступен, мне нужно было установить флаг #enable-generic-sensor-extra-classes в about:flags и запросить разрешение 'ambient-light-sensor' . Как и раньше, я использую прогрессивное улучшение, чтобы загружать соответствующий код только тогда, когда поддерживается API.

if ('AmbientLightSensor' in window) {
  import('/als.js');
}

Датчик возвращает значение окружающего освещения в люксах всякий раз, когда доступны новые показания. На основе таблицы значений типичных ситуаций освещения я придумал очень простую формулу для преобразования значения люкс в значение от 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 Calculator и ознакомьтесь с исходным кодом на Glitch . (Чтобы установить приложение, вам необходимо открыть его в отдельном окне. Встроенная версия, указанная ниже, не будет запускать мини-информационную панель.)

Счастливого дизайнерского декабря !