Nous écrivons front-end qui fonctionne. Ou comment devenir ingénieur d'un développeur. Partie 2

Donc, dans la deuxième partie, nous commençons la mise en œuvre. Décidons d'abord de la technologie. Je choisis des composants Web. L'approche des composants, l'api native, est facile à réutiliser et à déboguer.

Étape 1 - Décrivez les positions finales


La balise de notre fenêtre virtuelle sera appelée fenêtre personnalisée. Donc, nous décrivons d'abord les propriétés générales de la fenêtre:

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

Position modifiée:

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

Poste ouvert:

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

Position supprimée:

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


Étape 2 - Commencez à écrire un composant de fenêtre personnalisée


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

Nous implémentons les événements dragUp / dragDown

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

Schématiquement, le code ci-dessus peut être décrit comme suit:



nous pouvons donc maintenant distinguer les événements dragUp / dragDown. L'utilitaire suivant est le calcul de la réserve de marche.

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

Ici, nous nous souvenons d'abord de la réserve de puissance dont nous disposions au moment où le mouvement a commencé, puis ajoutons simplement deltaY à cette valeur et voyons si nous pouvons monter ou non.

En fait, dragUp logique:

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

Nous écrivons une méthode qui déplacera la fenêtre:

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

Examinons plus en détail ce qu'est this.lastPosY et comment il est calculé. Si en CSS, nous avons écrit transform: translateY (calc (100% - 50vh)); où 100% est la hauteur de la fenêtre virtuelle elle-même, et 50vh est la moitié de la hauteur de la fenêtre réelle, et cela correspond bien à une description statique de la position, alors il est plus pratique d'utiliser des valeurs absolues pour calculer le mouvement dans la dynamique, nous faisons ces transformations ici.

Donc, this.lastPosY est la quantité de mouvement de la fenêtre virtuelle en pixels au début du mouvement, nous y ajoutons this.deltaY et obtenons une nouvelle position de fenêtre.

Depuis que nous avons défini les propriétés:

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

alors notre système de coordonnées pour compter le mouvement de la fenêtre prendra la forme



Nous décrivons dragDown:

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

En fait, l'événement dragEnd:

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

Dans la ligne if (Math.abs (deltaY) <10), nous indiquons que si vous avez déplacé moins de 10 pixels, laissez la position actuelle.

En conséquence, nous devrions obtenir un composant comme


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

Ce code n'est pas une implémentation complète, mais seulement un prototype. Une étude plus détaillée du parchemin, des rebonds, de toute autre optimisation, touchcancel - laissée au lecteur.

All Articles