نكتب الواجهة التي تعمل. أو كيف تصبح مهندسًا من مطور. الجزء 2

لذا ، في الجزء الثاني نبدأ التنفيذ. أولاً ، دعنا نقرر التكنولوجيا. أختار مكونات الويب. من السهل إعادة استخدام وتصحيح النهج المكون ، واجهة برمجة التطبيقات الأصلية.

الخطوة 1 - وصف مواقف النهاية


سيتم تسمية علامة إطار العرض الافتراضي لدينا باسم منفذ العرض المخصص. لذا ، أولاً ، نصف الخصائص العامة لإطار العرض:

custom-viewport {
min-height: 50vh;
max-height: 100vh;
width: 100%;
position: absolute;
bottom: 0;
overflow: hidden;
transform-origin: 50% 100% 0;
}

الموقع المحدد:

custom-viewport[data-mode = "inited"] {
transform: translateY(calc(100% - 50vh));
transition: transform 1s;
}

تم فتح المركز:

custom-viewport[data-mode = "opened"] {
transform: translateY(0);
transition: transform 1s;
overflow-y: scroll;
}

الموضع المحذوف:

custom-viewport[data-mode = "deleted"] {
transform: translateY(100%);
transition: transform 1s;
}


الخطوة 2 - ابدأ كتابة مكون إطار العرض المخصص


class CustomViewport extends HTMLElement {
 constructor() {
  super();
 }
}

نقوم بتنفيذ أحداث السحب / السحب

class CustomViewport extends HTMLElement {
 constructor() {
  super();
 }
 connectedCallback() {
  this.addEventListener("touchstart", ev => {
   this.firstTouch = ev.touches[0];
  });
  this.addEventListener("touchmove", ev => {
   this.deltaY = ev.touches[0].clientY - this.firstTouch.clientY;
   return this.deltaY > 0 ? this.dragDown(ev) : this.dragUp(ev);
  });
 }
 dragUp(ev) {}
 dragDown(ev) {}
}

من الناحية التخطيطية ، يمكن وصف الرمز أعلاه على النحو التالي.



لذا ، يمكننا الآن التمييز بين أحداث السحب / السحب. الأداة المساعدة التالية هي حساب احتياطي الطاقة.

class CustomViewport extends HTMLElement {
 constructor() {
  super();
  this.VIEWPORT_HEIGHT = window.innerHeight; // +
 }
 connectedCallback() {
  this.addEventListener("touchstart", ev => {
   this.firstTouch = ev.touches[0];
   const rect = this.getBoundingClientRect(); // +
   const { height, top } = rect; // +
   this.bottomOffsetBeforeDragging = (height + top) - this.VIEWPORT_HEIGHT; // +
  });
  this.addEventListener("touchmove", ev => {
   this.deltaY = ev.touches[0].clientY - this.firstTouch.clientY;
   return this.deltaY > 0 ? this.dragDown() : this.dragUp();
  });
 }
 dragUp() {}
 dragDown() {}
 isBottomOffset() { // +
   return (this.bottomOffsetBeforeDragging + this.deltaY) > 0; // +
 } // +
}

هنا نتذكر أولاً مقدار احتياطي الطاقة الذي كان لدينا في اللحظة التي بدأت فيها الحركة ، ثم نضيف ببساطة دلتا إلى هذه القيمة ونرى ما إذا كان بإمكاننا التحرك لأعلى أم لا.

سحب المنطق في الواقع:

...
dragUp() {
 if(this.isBottomOffset()) {
  //  
  return;
 }
 this.style.transform = 'translateY(0)';
}
...

نكتب طريقة ستنقل إطار العرض:

class CustomViewport extends HTMLElement {
 constructor() {
  super();
  this.VIEWPORT_HEIGHT = window.innerHeight;
 }
 connectedCallback() {
  this.addEventListener("touchstart", ev => {
   this.firstTouch = ev.touches[0];
   const rect = this.getBoundingClientRect();
   const { height, top } = rect;
   this.bottomOffsetBeforeDragging = (height + top) - this.VIEWPORT_HEIGHT;
   this.lastPosY = this.bottomOffsetBeforeDragging - this.scrollTop; // +
  });
   ...
 }
translateY() { // +
  const pixels = this.deltaY + this.lastPosY; // +
  this.style.transform = `translateY(${pixels}px)`; // +
  this.style.transition = 'none'; // +
 } // +
...
}

دعونا نفحص بمزيد من التفصيل ماهية هذا الوضع. وآخر طريقة حسابه. إذا كتبنا في css convert: translateY (calc (100٪ - 50vh)) ؛ حيث 100٪ هو ارتفاع إطار العرض الافتراضي نفسه ، و 50 vh هو نصف ارتفاع إطار العرض الحقيقي ، وهذا يناسب تمامًا الوصف الثابت للموضع ، فمن الأنسب العمل بقيم مطلقة لحساب الحركة في الديناميكيات ، نحن نقوم بهذه التحولات هنا.

لذا ، هذا POSY هو مقدار حركة إطار العرض الافتراضي بالبكسل في بداية الحركة ، وعلينا أن نضيف هذا deltaY ونحصل على موضع إطار عرض جديد.

بما أننا حددنا الخصائص:

bottom: 0;
transform-origin: 50% 100% 0;

ثم يأخذ نظام الإحداثيات الخاص بنا لحساب حركة إطار العرض الشكل الذي



نصفه السحب:

...
dragDown() {
 if(this.lastPosY < 0) {
  return;
 }
 this.translateY();
}
...

حدث السحب في النهاية:

class CustomViewport extends HTMLElement {
 constructor() {
  super();
  this.VIEWPORT_HEIGHT = window.innerHeight;
 }
 connectedCallback() {
  this.addEventListener("touchend", ev => { // +
   const { mode: currentMode } = this.dataset; // +
   this.style = null; // +
   if (Math.abs(deltaY) < 10) { // +
    this.dataset.mode = currentMode; // +
    return; // +
   } // +
    if (deltaY > 0) { // +
     if (currentMode === "inited") { // +
       this.dataset.mode = "deleted"; // +
       return; // +
      } // +
      this.dataset.mode = "inited"; // +
      return; // +
    } // +
    this.dataset.mode = "opened"; // +
  }); // +
   ...

في السطر إذا (Math.abs (deltaY) <10) نشير إلى أنه إذا قمت بنقل أقل من 10 بكسل ، فاترك الموضع الحالي.

نتيجة لذلك ، يجب أن نحصل على مكون مثل


class CustomViewport extends HTMLElement {
 constructor() {
  super();
  this.VIEWPORT_HEIGHT = window.innerHeight;
 }
 connectedCallback() {
  this.addEventListener("touchstart", ev => {
   this.firstTouch = ev.touches[0];
   const rect = this.getBoundingClientRect();
   const { height, top } = rect;
   this.bottomOffsetBeforeDragging = (height + top) - this.VIEWPORT_HEIGHT;
   this.lastPosY = this.bottomOffsetBeforeDragging - this.scrollTop;
  });
  this.addEventListener("touchmove", ev => {
   this.deltaY = ev.touches[0].clientY - this.firstTouch.clientY;
   return this.deltaY > 0 ? this.dragDown() : this.dragUp();
  });
  this.addEventListener("touchend", ev => {
   const { mode: currentMode } = this.dataset;
   this.style = null;
   if (Math.abs(this.deltaY) < 10) {
    this.dataset.mode = currentMode;
    return;
   }
    if (this.deltaY > 0) {
     if (currentMode === "inited") {
       this.dataset.mode = "deleted";
       return;
      }
      this.dataset.mode = "inited";
      return;
    }
    this.dataset.mode = "opened";
  });
 }
 dragUp() {
  if(this.isBottomOffset()) {
   this.translateY();
   return;
  }
  this.style.transform = 'translateY(0)';
}
 dragDown() {
   if(this.lastPosY < 0) {
   return;
  }
  this.translateY();
}
translateY() {
  const pixels = this.deltaY + this.lastPosY;
  this.style.transform = `translateY(${pixels}px)`;
  this.style.transition = 'none';
 }
 isBottomOffset() {
   return (this.bottomOffsetBeforeDragging + this.deltaY) > 0;
 }
}

customElements.define('custom-viewport', CustomViewport);

هذا الرمز ليس تطبيقًا كاملاً ، ولكنه مجرد نموذج أولي. دراسة أكثر تفصيلاً عن التمرير ، والتنقيب ، وأي تحسينات أخرى ، لمس اللمس - تُرك للقارئ.

All Articles