מחשבון Designcember

ניסיון סקאומורפי ליצור מחדש מחשבון סולארי באינטרנט באמצעות window Controls Overlay API ו-Ambient Light Sensor API

האתגר

אני ילד של שנות ה-80. אחד מהדברים שעוררו כעס כשהייתי בתיכון היה מחשבונים סולאריים. כולנו קיבלנו מבית הספר TI-30X SOLAR, ואני מאוד אוהב את העובדה שהמחשבונים שלנו השווצים זה לזה על ידי חישוב הפקטורים של 69, המספר הגבוה ביותר ש-TI-30X יכול להתמודד איתו. (ההבדלים במהירות היו גדולים מאוד, עדיין לא ברור לי למה).

עכשיו, כמעט 28 שנים מאוחר יותר, חשבתי שיהיה אתגר מגניב ב- Designcember ליצור מחדש את המחשבון ב-HTML, ב-CSS וב-JavaScript. אני לא מעצבת הרבה, לא התחלתי מאפס, אלא עם CodePen של Sassja Ceballos.

תצוגת CodePen עם חלוניות HTML, CSS ו-JS מוערמות בצד שמאל, והתצוגה המקדימה של המחשבון בצד ימין.

כדאי להתקין את האפליקציה

למרות שזו לא התחלה טובה, החלטתי להגביר את תחושת המגניבות הסקומורפית המלאה. השלב הראשון היה ליצור PWA כדי שאפשר יהיה להתקין אותה. אני מתחזק תבנית בסיס של PWA ב-Glitch שאפשר ליצור בה רמיקס בכל פעם שצריך הדגמה קצרה. קובץ השירות (service worker) לא יזכה בפרס כלשהו לתכנות, והוא בהחלט לא מוכן להפקה, אבל הוא מספיק כדי להפעיל את המיני-סרגל של 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 כשהוא יהיה זמין.

{
  "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 פועל במצב עצמאי עם התכונה &#39;שכבת-על של פקדי החלונות&#39;. באלפבית של המחשבון יופיע הכיתוב &#39;Google&#39;.

תא סולארי שפועל בפועל

בתור החנונית האולטימטיבית, מובן לי שהיה צריך להפעיל את התא הסולארי באמת. המחשבון אמור לפעול רק אם יש מספיק אור. יצרנו את המודל כדי להגדיר את opacity של הספרות במסך באמצעות משתנה 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'. כמו קודם, אני משתמש ב-Progressive שיפור כדי לטעון את הקוד הרלוונטי רק כשיש תמיכה ב-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, שבדקתי בעבר, כבר עבר כברת דרך ארוכה.

הדגמה (דמו)

אל תשכחו להתנסות בהדגמה של מחשבון העיצוב ולקרוא את קוד המקור ב-Glitch. (כדי להתקין את האפליקציה, עליכם לפתוח אותה בחלון נפרד. הגרסה המוטמעת שלמטה לא תפעיל את סרגל המידע הקטן.)

עיצוב שיער שמח!