ماشین‌حساب Designcember

تلاشی واقع‌گرایانه برای بازآفرینی ماشین‌حساب خورشیدی در وب بااستفاده از Window Controls Overlay API و Ambient Light Sensor API.

چالش

من بچه دهه ۱۹۸۰ هستم. چیزی که وقتی من در دبیرستان بودم خیلی مد بود، ماشین حساب‌های خورشیدی بود. مدرسه به همه ما یک TI-30X SOLAR داده بود، و من خاطرات خوبی از مقایسه ماشین‌حساب‌هایمان با یکدیگر با محاسبه فاکتوریل ۶۹ دارم، بالاترین عددی که TI-30X می‌توانست پردازش کند. (تفاوت سرعت بسیار قابل‌اندازه‌گیری بود، هنوز نمی‌دانم چرا.)

اکنون، تقریباً ۲۸ سال بعد، فکر کردم که بازسازی ماشین‌حساب در HTML،‏ CSS، و JavaScript چالش جالبی برای Designcember خواهد بود. ازآنجایی‌که طراح خوبی نیستم، از ابتدا شروع نکردم، بلکه با یک CodePen از Sassja Ceballos شروع کردم.

نمای CodePen با پانل‌های HTML،‏ CSS، و JS پشته‌ای در سمت چپ و پیش‌نمایش ماشین‌حساب در سمت راست.

برنامه وب را نصب‌شدنی کنید

اگرچه شروع بدی نبود، تصمیم گرفتم آن را تقویت کنم تا به یک طراحی واقع‌گرایانه فوق‌العاده تبدیل شود. اولین قدم این بود که آن را به یک 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" />

«ماشین‌حساب Designcember» درحال اجرا در حالت تمام‌صفحه در تلفن Pixel 6 Pro.

درحال ترکیب با رایانه

در رایانه، ویژگی جالبی وجود دارد که می‌توانم از آن استفاده کنم: رونهاد کنترل‌های پنجره که به من امکان می‌دهد محتوا را در ستون عنوان پنجره برنامه قرار دهم. اولین گام این است که توالی بازگشت حالت نمایش را لغو کنید تا ابتدا از 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 من از بالا شروع شود و رنگ زمینه را هم تغییر می‌دهم. I can detect if the window controls overlay is visible by checking the navigator.windowControlsOverlay.visible property.

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 است. اکنون می‌توانم ماشین‌حساب را آزادانه روی میز کارم قرار دهم و با کلیک کردن روی نماد chevron در گوشه بالا سمت چپ، ویژگی کنترل‌های پنجره را فعال کنم.

«ماشین‌حساب Designcember» در حالت مستقل با ویژگی فعال «هم‌پوشانی کنترل‌های پنجره» اجرا می‌شود. نمایشگر کلمه «Google» را با الفبای ماشین‌حساب هجی می‌کند.

سلول خورشیدی که واقعاً کار می‌کند

برای نهایت خوره بازی، البته من نیاز داشتم که سلول خورشیدی را واقعاً به کار بیندازم. ماشین‌حساب فقط درصورت وجود نور کافی باید کار کند. روشی که من این را مدل‌سازی کردم، ازطریق تنظیم CSS opacity ارقام روی نمایشگر ازطریق متغیر CSS --opacity است که آن را ازطریق جاوا اسکریپت کنترل می‌کنم.

:root {
  --opacity: 0.75;
}

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

برای تشخیص اینکه آیا نور کافی برای کار کردن ماشین‌حساب وجود دارد یا نه، از AmbientLightSensor API استفاده می‌کنم. برای اینکه این میانای برنامه‌سازی کاربردی دردسترس باشد، باید پرچم #enable-generic-sensor-extra-classes را در about:flags تنظیم می‌کردم و اجازه 'ambient-light-sensor' را درخواست می‌کردم. مثل قبل، از ارتقای پیش‌رو استفاده می‌کنم تا فقط وقتی میانای برنامه‌سازی کاربردی پشتیبانی می‌شود، کد مربوطه را بار کنم.

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

هرگاه خوانش جدیدی دردسترس باشد، حسگر نور محیط را برحسب واحد لوکس برمی‌گرداند. براساس جدول مقادیر موقعیت‌های نوری معمول، به فرمول بسیار ساده‌ای رسیدم تا مقدار لوکس را به مقداری بین ۰ و ۱ تبدیل کنم که به‌صورت برنامه‌نویسی‌شده به متغیر --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 بازی کنید و کد منبع را در GitHub بررسی کنید. (برای نصب برنامه، باید آن را در پنجره خودش باز کنید. نسخه جاسازی‌شده زیر نوار اطلاعات کوچک را راه‌اندازی نمی‌کند.)

Designcember مبارک!