Wir schreiben ein Frontend, das funktioniert. Oder wie man Ingenieur von einem Entwickler wird. Teil 2

Im zweiten Teil beginnen wir mit der Implementierung. Lassen Sie uns zunächst die Technologie festlegen. Ich wähle Webkomponenten. Der Komponentenansatz, native API, ist einfach wiederzuverwenden und zu debuggen.

Schritt 1 - Beschreiben Sie die Endpositionen


Das Tag für unser virtuelles Ansichtsfenster wird als benutzerdefiniertes Ansichtsfenster bezeichnet. Zunächst beschreiben wir die allgemeinen Eigenschaften für das Ansichtsfenster:

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

Position inited:

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

Position geöffnet:

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

Gelöschte Position:

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


Schritt 2 - Beginnen Sie mit dem Schreiben einer benutzerdefinierten Ansichtsfenster-Komponente


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

Wir implementieren DragUp / DragDown-Ereignisse

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) {}
}

Der obige Code kann schematisch wie folgt beschrieben werden.



Nun können wir zwischen DragUp / DragDown-Ereignissen unterscheiden. Das nächste Dienstprogramm ist die Berechnung der Gangreserve.

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; // +
 } // +
}

Hier erinnern wir uns zuerst daran, wie viel Gangreserve wir zu Beginn der Bewegung hatten, und dann addieren wir einfach DeltaY zu diesem Wert und sehen, ob wir nach oben gehen können oder nicht.

Eigentlich logisches DragUp:

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

Wir schreiben eine Methode, die das Ansichtsfenster verschiebt:

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'; // +
 } // +
...
}

Lassen Sie uns genauer untersuchen, was this.lastPosY ist und wie es berechnet wird. Wenn wir in CSS transform geschrieben haben: translateY (calc (100% - 50vh)); Wenn 100% die Höhe des virtuellen Ansichtsfensters selbst ist und 50vh die Hälfte der Höhe des realen Ansichtsfensters ist und dies gut für eine statische Beschreibung der Position geeignet ist, ist es bequemer, mit absoluten Werten zu arbeiten, um die Bewegung in der Dynamik zu berechnen. Wir führen diese Transformationen hier durch.

This.lastPosY ist also der Bewegungsumfang des virtuellen Ansichtsfensters in Pixel zu Beginn der Bewegung. Wir fügen this.deltaY hinzu und ermitteln die neue Position des Ansichtsfensters.

Da haben wir die Eigenschaften definiert:

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

Dann hat unser Koordinatensystem zum Zählen der Bewegung des Ansichtsfensters die Form



DragDown:

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

Eigentlich DragEnd-Ereignis:

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"; // +
  }); // +
   ...

In der Zeile if (Math.abs (deltaY) <10) geben wir an, dass Sie die aktuelle Position verlassen, wenn Sie weniger als 10 Pixel verschoben haben.

Als Ergebnis sollten wir eine Komponente wie erhalten


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);

Dieser Code ist keine vollständige Implementierung, sondern nur ein Prototyp. Eine detailliertere Untersuchung der Schriftrolle, Entprellen, sonstige Optimierungen, Touchcancel - dem Leser überlassen.

All Articles