นำหลักการเขียนโปรแกรมแอปขนาดเล็กไปใช้กับโปรเจ็กต์ตัวอย่าง

โดเมนของแอป

หากต้องการแสดงวิธีเขียนโปรแกรมแบบ มินิแอป ที่ใช้กับเว็บแอป ผมจำเป็นต้องมีไอเดียแอปขนาดเล็กแต่สมบูรณ์พอ การออกกำลังกายแบบ HIIT (High-Intensity Interval Training) เป็นกลยุทธ์การออกกำลังกายแบบคาร์ดิโอที่สลับเซ็ตการออกกำลังกายแบบไม่ใช้ออกซิเจนที่มีความเข้มข้นสูงในช่วงเวลาสั้นๆ กับช่วงพักฟื้นที่เบาลง การฝึกแบบ HIIT หลายๆ แบบใช้ตัวจับเวลา HIIT เช่น เซสชันออนไลน์ 30 นาที จากช่อง YouTube ของ The Body Coach TV

เซสชันการฝึก HIIT ออนไลน์พร้อมตัวจับเวลาความเข้มข้นสูงสีเขียว
ช่วงออกกำลังกาย
เซสชันการฝึก HIIT ออนไลน์พร้อมตัวจับเวลาความเข้มข้นต่ำสีแดง
ช่วงพัก

แอปตัวอย่าง HIIT Time

สำหรับบทนี้ ผมได้สร้างแอปพลิเคชันตัวจับเวลา HIIT พื้นฐานที่ชื่อว่า "HIIT Time" ซึ่งช่วยให้ผู้ใช้กำหนดและจัดการตัวจับเวลาต่างๆ ได้ โดยตัวจับเวลาแต่ละตัวจะประกอบด้วยช่วงเวลาที่มีความเข้มข้นสูงและต่ำเสมอ จากนั้นก็เลือกตัวจับเวลาตัวใดตัวหนึ่งสำหรับเซสชันการฝึก แอปนี้เป็นแอปที่ปรับเปลี่ยนตามขนาดหน้าจอได้ โดยมีแถบนำทาง แถบแท็บ และ 3 หน้าดังนี้

  • Workout: หน้าที่ใช้งานอยู่ระหว่างการออกกำลังกาย ซึ่งช่วยให้ผู้ใช้เลือกตัวจับเวลาตัวใดตัวหนึ่งได้ และมีวงแหวนความคืบหน้า 3 วง ได้แก่ จำนวนเซ็ต ช่วงออกกำลังกาย และช่วงพัก
  • Timers: จัดการตัวจับเวลาที่มีอยู่และช่วยให้ผู้ใช้สร้างตัวจับเวลาใหม่ได้
  • Preferences: อนุญาตให้เปิด/ปิดเอฟเฟกต์เสียงและเอาต์พุตเสียงพูด รวมถึงเลือกภาษาและธีม

ภาพหน้าจอด้านล่างแสดงให้เห็นภาพรวมของแอปพลิเคชัน

แอปตัวอย่าง HIIT Time ในโหมดแนวตั้ง
แท็บ "Workout" ของ HIIT Time ในโหมดแนวตั้ง
แอปตัวอย่าง HIIT Time ในโหมดแนวนอน
แท็บ "Workout" ของ HIIT Time ในโหมดแนวนอน
แอปตัวอย่าง HIIT Time แสดงการจัดการตัวจับเวลา
การจัดการตัวจับเวลาของ HIIT Time

โครงสร้างแอป

ตามที่ระบุไว้ข้างต้น แอปประกอบด้วยแถบนำทาง แถบแท็บ และ 3 หน้าที่จัดเรียงเป็นตารางกริด แถบนำทางและแถบแท็บได้รับการสร้างขึ้นเป็น iframe โดยมีคอนเทนเนอร์ <div> อยู่ระหว่าง iframe เหล่านั้น และมี iframe อีก 3 รายการ สำหรับหน้าต่างๆ ซึ่งจะมี iframe หนึ่งรายการที่มองเห็นได้เสมอและขึ้นอยู่กับการเลือกที่ใช้งานอยู่ในแถบแท็บ iframe สุดท้ายที่ชี้ไปยัง about:blank ใช้สำหรับหน้าในแอปที่สร้างขึ้นแบบไดนามิก ซึ่งจำเป็นสำหรับการแก้ไขตัวจับเวลาที่มีอยู่หรือสร้างตัวจับเวลาใหม่ ผมเรียกรูปแบบนี้ว่าแอปหน้าเดียวแบบหลายหน้า (MPSPA)

มุมมองเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ของโครงสร้าง HTML ของแอปที่แสดงว่าประกอบด้วย iframe 6 รายการ ได้แก่ iframe 1 รายการสำหรับแถบนำทาง, iframe 1 รายการสำหรับแถบแท็บ และ iframe 3 รายการที่จัดกลุ่มไว้สำหรับแต่ละหน้าของแอป โดยมี iframe ตัวยึดตำแหน่งสุดท้ายสำหรับหน้าแบบไดนามิก
แอปประกอบด้วย iframe 6 รายการ

มาร์กอัป lit-html ที่อิงตามคอมโพเนนต์

โครงสร้างของแต่ละหน้าได้รับการสร้างขึ้นเป็น โครงสร้างพื้นฐานของ lit-html ที่ได้รับการประเมินแบบไดนามิกในขณะรันไทม์ สำหรับข้อมูลพื้นฐานเกี่ยวกับ lit-html นั้นเป็นไลบรารีการสร้างเทมเพลต HTML ที่มีประสิทธิภาพ แสดงออกได้ดี และขยายได้สำหรับ JavaScript การใช้ไลบรารีนี้โดยตรงในไฟล์ HTML จะทำให้โมเดลการเขียนโปรแกรมในใจมุ่งเน้นไปที่เอาต์พุตโดยตรง ในฐานะโปรแกรมเมอร์ คุณจะเขียนเทมเพลตของเอาต์พุตสุดท้ายที่จะมีลักษณะอย่างไร จากนั้น lit-html จะเติมช่องว่างแบบไดนามิกตามข้อมูลของคุณและเชื่อมต่อตัวฟังเหตุการณ์ แอปใช้คอมโพเนนต์ที่กำหนดเองของบุคคลที่สาม เช่น Shoelace's <sl-progress-ring> หรือคอมโพเนนต์ที่กำหนดเองที่ผมสร้างขึ้นเองที่เรียกว่า <human-duration> เนื่องจากองค์ประกอบที่กำหนดเองมี API แบบประกาศ (เช่น แอตทริบิวต์ percentage ของวงกลมแสดงความคืบหน้า) องค์ประกอบเหล่านี้จึงทำงานร่วมกับ lit-html ได้ดี ดังที่คุณเห็นในรายการด้านล่าง

<div>
  <button class="start" @click="${eventHandlers.start}" type="button">
    ${strings.START}
  </button>
  <button class="pause" @click="${eventHandlers.pause}" type="button">
    ${strings.PAUSE}
  </button>
  <button class="reset" @click="${eventHandlers.reset}" type="button">
    ${strings.RESET}
  </button>
</div>

<div class="progress-rings">
  <sl-progress-ring
    class="sets"
    percentage="${Math.floor(data.sets/data.activeTimer.sets*100)}"
  >
    <div class="progress-ring-caption">
      <span>${strings.SETS}</span>
      <span>${data.sets}</span>
    </div>
  </sl-progress-ring>
</div>
ปุ่ม 3 ปุ่มและวงกลมแสดงความคืบหน้า
ส่วนของหน้าที่แสดงผลซึ่งสอดคล้องกับมาร์กอัปด้านบน

โมเดลการเขียนโปรแกรม

แต่ละหน้าจะมีคลาส Page ที่สอดคล้องกัน ซึ่งจะเติมมาร์กอัป lit-html ให้มีชีวิตชีวาด้วยการให้การใช้งานตัวจัดการเหตุการณ์และให้ข้อมูลสำหรับแต่ละหน้า คลาสนี้ยังรองรับเมธอดวงจรชีวิต เช่น onShow(), onHide(), onLoad() และ onUnload() หน้าต่างๆ มีสิทธิ์เข้าถึงที่เก็บข้อมูลที่ใช้สำหรับการแชร์สถานะต่อหน้าและสถานะส่วนกลางที่อาจมีการคงอยู่ ระบบจะจัดการสตริงทั้งหมดจากส่วนกลาง จึงมีการสร้างการแปลเป็นภาษาต่างๆ ไว้ในตัว เบราว์เซอร์จะจัดการการกำหนดเส้นทางให้โดยแทบไม่มีค่าใช้จ่าย เนื่องจากสิ่งที่แอปทำคือการสลับการมองเห็น iframe และเปลี่ยนแอตทริบิวต์ src ของ iframe ตัวยึดตำแหน่งสำหรับหน้าที่สร้างขึ้นแบบไดนามิก ตัวอย่างด้านล่างแสดงโค้ดสำหรับการปิดหน้าที่สร้างขึ้นแบบไดนามิก

import Page from '../page.js';

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
หน้าในแอปที่รับรู้เป็น iframe
การนำทางเกิดขึ้นจาก iframe ไปยัง iframe

การจัดรูปแบบ

การจัดรูปแบบหน้าจะเกิดขึ้นต่อหน้าในไฟล์ CSS ที่มีขอบเขตของตัวเอง ซึ่งหมายความว่าโดยปกติแล้วองค์ประกอบสามารถระบุได้โดยตรงตามชื่อองค์ประกอบ เนื่องจากไม่มีการขัดแย้งกับหน้าอื่นๆ ระบบจะเพิ่มรูปแบบส่วนกลางลงในแต่ละหน้า ดังนั้นจึงไม่จำเป็นต้องประกาศการตั้งค่าส่วนกลางซ้ำๆ เช่น font-family หรือ box-sizing นอกจากนี้ยังเป็นที่ที่กำหนดตัวเลือกธีมและโหมดมืดด้วย รายการด้านล่างแสดงกฎสำหรับหน้าการตั้งค่า ซึ่งจะจัดวางองค์ประกอบแบบฟอร์มต่างๆ ในตารางกริด

main {
  max-width: 600px;
}

form {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: 0.5rem;
  margin-block-end: 1rem;
}

label {
  text-align: end;
  grid-column: 1 / 2;
}

input,
select {
  grid-column: 2 / 3;
}
หน้าค่ากำหนดของแอป HIIT Time ที่แสดงแบบฟอร์มในเลย์เอาต์ตารางกริด
ทุกหน้าเป็นโลกของตัวเอง การจัดรูปแบบจะเกิดขึ้นโดยตรงกับชื่อองค์ประกอบ

Wake Lock หน้าจอ

หน้าจอไม่ควรปิดระหว่างการออกกำลังกาย ในเบราว์เซอร์ที่รองรับ HIIT Time จะทำให้เป็นจริงได้ผ่าน Wake Lock หน้าจอ ข้อมูลโค้ดด้านล่างแสดงวิธีดำเนินการ

if ('wakeLock' in navigator) {
  const requestWakeLock = async () => {
    try {
      page.shared.wakeLock = await navigator.wakeLock.request('screen');
      page.shared.wakeLock.addEventListener('release', () => {
        // Nothing.
      });
    } catch (err) {
      console.error(`${err.name}, ${err.message}`);
    }
  };
  // Request a screen wake lock…
  await requestWakeLock();
  // …and re-request it when the page becomes visible.
  document.addEventListener('visibilitychange', async () => {
    if (
      page.shared.wakeLock !== null &&
      document.visibilityState === 'visible'
    ) {
      await requestWakeLock();
    }
  });
}

การทดสอบแอปพลิเคชัน

แอปพลิเคชัน HIIT Time พร้อมใช้งานใน GitHub คุณสามารถลองใช้เดโมในหน้าต่างใหม่ หรือใน iframe ที่ฝังไว้ด้านล่างนี้ ซึ่งจำลองอุปกรณ์เคลื่อนที่

คำขอบคุณ

บทความนี้ได้รับการตรวจสอบโดย Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent, และ Keith Gu.