Les icônes tremblantes et les appuis longs sont devenus quelque chose que nous connaissons très bien sur nos écrans de téléphone. Sur les iPhones en particulier, le fait de secouer les icônes implique généralement qu’elles sont déplaçables et modifiables, tandis que les appuis longs sont devenus le moyen normal d’obtenir des options supplémentaires.
Dans ce tutoriel, nous allons voir comment recréer ces effets en Javascript et CSS. Dans ce tutoriel, nous aborderons :
- Nouvelles fonctionnalités CSS– tels que les flous d’arrière-plan et les boîtes flexibles animées.
- Appui long— comment créer l’effet d’appui long avec Javascript.
- Faire glisser— comment créer un simple système de glisser-déposer en Javascript.
Démo
Comme toujours, commençons par la démo. C’est ce que nous prévoyons de créer aujourd’hui.
- Si vous cliquez et appuyez sur une icône pendant une seconde, une fenêtre contextuelle s’affichera.
- Si vous cliquez et appuyez pendant deux secondes au total, les icônes commenceront à trembler, comme sur iPhone. Cela fonctionnera également sur mobile.
Étape 1 : HTML
Dans ce tutoriel, je ne me concentrerai pas trop sur le HTML car il est assez rudimentaire – il suffit de dire que le HTML pour cette démo est composé de :
- Conteneurs d’icônes – un conteneur div avec toutes les informations sur les icônes individuelles.
- Une couverture – une couverture div qui se trouve au-dessus de toute la démo, qui sera floue si l’utilisateur appuie longuement pour afficher le sous-menu.
- Quelques notifications — quelques notifications en bas pour donner des instructions sur ce qu’il faut faire.
- Un sous-menu — un sous-menu qui se déplace lorsque l’utilisateur clique sur diverses icônes.
Étape 2 : CSS
La plupart des CSS sont assez basiques, cependant, je vais citer quelques éléments importants et intéressants que j’ai utilisés et qui sont assez typiques de la conception d’interface utilisateur HTML. Notre objectif est donc de rendre les icônes déplaçables, mais il y a beaucoup de choses qui peuvent affecter cela. D’une part, nous avons une couverture qui apparaît pour brouiller l’arrière-plan. Ensuite, nous avons ces images embêtantes, que la plupart des navigateurs permettent aux utilisateurs de faire glisser.
Pour contourner la plupart de ces problèmes, nous pouvons utiliser une propriété et une valeur en CSS appelées pointer-events: none;
. Cela signifie que l’utilisateur ne peut pas interagir avec nos éléments HTML. Ainsi, sur la couverture floue, nous supprimons les événements de pointeur lorsqu’ils ne sont pas flous et les ajoutons lorsque l’arrière-plan est flou. Cela signifie que l’utilisateur clique normalement sur cet élément HTML, jusqu’à ce que nous voulions rendre l’arrière-plan flou, auquel cas il devient actif :
#iphone .cover {
position: absolute;
top: -1rem;
backdrop-filter: blur(4px);
left: -1rem;
width: calc(100% + 2rem);
opacity: 0;
z-index: 99999;
height: calc(100% + 2rem);
transition: all 0.2s ease-out;
background: rgba(0,0,0,0);
pointer-events: none; /* No pointer events! */
}
[data-dropdown="true"] #iphone .cover {
pointer-events: all; /* All pointer events */
opacity: 1;
}
#iphone .icon-container img {
width: calc(100% + 2px);
pointer-events: none; /* No pointer events */
}
Filtre de toile de fond
Cela nous amène à une autre propriété CSS qui peut être très utile —backdrop-filter
. Avec la prise en charge de tous les principaux navigateurs à l’exception de Firefox, les filtres de fond nous permettent d’ajouter des effets aux éléments derrière une balise HMTL si la balise est légèrement transparente. Dans notre .cover
CSS, nous l’avons défini comme suit :
backdrop-filter: blur(4px);
Suppression de l’icône d’animation
Pour toute cette démo, je voulais que la suppression des icônes soit fluide. Les icônes sont disposées selon une flexbox. Pour ce faire, j’ai créé une animation personnalisée qui fait que l’icône est mise à l’échelle jusqu’à zéro (de sorte qu’il semble qu’elle s’éloigne) – puis réduit sa largeur en tandem. L’effet est que les icônes semblent se fermer les unes sur les autres lorsqu’une est supprimée, créant une animation fluide.
Cette animation est appliquée dans un petit morceau de Javascript lorsque l’utilisateur clique sur un bouton de suppression.
@keyframes scaleBack {
0% {
transform: scale(1);
width: 5rem;
}
40% {
opacity: 1;
}
50% {
width: 5rem;
opacity: 0;
transform: scale(0.0001);
}
100% {
width: 0rem;
transform: scale(0.0001);
}
}
Points positifs à l’utilisation des transformations
- Vous permet d’utiliser le positionnement en haut, en bas, à gauche et à droite pour d’autres choses.
- Utilise l’accélération 3D, il s’anime donc très rapidement !
Étape 3 : Javascript
Le Javascript ici n’est pas super compliqué – et en fait, une grande partie de la complexité vient d’essayer de rendre les icônes déplaçables. Il y a essentiellement quelques événements que nous voulons suivre :
- Lorsque l’utilisateur clique sur une icône, nous voulons mesurer pendant combien de temps, nous pouvons donc afficher la liste déroulante ou l’effet de tremblement.
- Lorsque l’utilisateur clique sur le bouton, nous voulons supprimer les minuteries pour les appuis longs et réinitialiser tous les autres trackers de mouvement.
- Lorsque l’utilisateur clique sur le bouton de suppression, nous souhaitons supprimer cette icône.
- Lorsque l’utilisateur clique sur n’importe quoi sauf sur un bouton, nous voulons supprimer les effets.
Donc, comme vous l’avez peut-être deviné, nous utilisons des événements de pointeur. Je vous recommande toujours d’essayer d’utiliser des événements de pointeur si vous le pouvez, car ils sont à la fois compatibles avec les appareils mobiles et les ordinateurs de bureau. De cette façon, vous n’avez généralement besoin d’écrire qu’un seul script pour tous les appareils. Les événements de pointeur remplacent les gestionnaires d’événements tels que la souris enfoncée par le pointeur vers le bas.
État de suivi
Pour suivre l’utilisateur, nous avons deux états : secouant et déroulant. Nous utilisons des attributs de données dans la balise HTML pour suivre cela – si les icônes tremblent, alors data-shaking="true"
sur la balise body, et si une liste déroulante est visible, alors data-dropdown="true"
est indiqué sur l’étiquette du corps. Nous procédons de cette façon afin de pouvoir modifier le CSS des éléments en fonction de l’état.
Nous avons alors la presse longue. Pour gérer les appuis longs, il suffit de :
- Créer une variable pointeur vers le bas— cette variable est définie sur true immédiatement lorsque l’utilisateur clique et sur false immédiatement lorsqu’il relâche.
- Créer des délais d’attente— nous vérifions après une seconde et deux secondes si le pointeur vers le bas est vrai. Si c’est le cas, alors nous faisons en sorte que les icônes affichent la liste déroulante ou secouent.
- Définir une fonction de réinitialisation— Nous réinitialisons toutes les variables si l’utilisateur abandonne l’appui long, afin que nous puissions les réutiliser s’il réessaye.
Dans le code, cela ressemble à ceci :
// For selection of the icons and removal icons
let icons = document.querySelectorAll('.icon-container');
let removals = document.querySelectorAll('.remove-icon');
// These all store data on the mouse position at different times in the code
let pointerdown = false;
let offset = 0;
let mouseXInit = 0;
let mouseYInit = 0;
let mouseX = 0;
let mouseY = 0;
let positionX = 0;
let positionY = 0;
let currentTop = 0;
let currentLeft = 0;
// This is for holding our timers
let timers = { first: undefined, second: undefined, third: undefined }
let helpers = {
reset: function(extended) {
// This is our reset - it sets everything back to zero, whenever we need to
// All variables and settings are reset
mouseX = 0;
mouseY = 0;
mouseXInit = 0;
mouseYInit = 0;
currentTop = 0;
currentLeft = 0;
offset = 0;
if(typeof timers.first !== "undefined") {
clearTimeout(timers.first);
}
if(typeof timers.second !== "undefined") {
clearTimeout(timers.second);
}
if(typeof timers.third !== "undefined") {
clearTimeout(timers.third);
}
if(typeof extended == "undefined") {
document.querySelector('.sub-menu').classList.remove('show-sub-menu');
document.body.setAttribute('data-shaking', false);
document.body.setAttribute('data-dropdown', false);
pointerdown = false;
icons.forEach(function(item) {
item.setAttribute('data-selected', false);
item.style.top = 0;
item.style.left = 0;
})
}
},
checkPoint: function(x, y, limit) {
// This checks if the users mouse has moved more than a certain limit. If it has, then they may be dragging..
// So we don't cause the long press animation
if(x < limit && x > limit * -1 && y < limit && y > limit * -1) {
return true;
} else {
return false;
}
}
}
// For every icon
icons.forEach(function(item) {
// Add a pointerdown event
item.addEventListener('pointerdown', function(e) {
// Get the click location and set pointerdown to true
pointerdown = true;
mouseXInit = e.pageX;
mouseYInit = e.pageY;
// Get the left and top position of the item, if any
currentTop = parseFloat(item.style.top) || 0;
currentLeft = parseFloat(item.style.left) || 0;
// Set a timer to wait for a hold click
timers.first = setTimeout(function() {
// Only do this if pointerdown is true, and if the user hasn't moved more than 10px while clicking down
if(pointerdown === true && document.body.getAttribute('data-shaking') !== "true" && helpers.checkPoint(mouseX, mouseY, 10)) {
// Icon is now selected, and the dropdown should appear
item.setAttribute('data-selected', true);
document.body.setAttribute('data-dropdown', true);
// Find out where exactly the icon is (x, y) coordinates
let left = item.getBoundingClientRect().left - document.querySelector('#iphone').getBoundingClientRect().left;
let bottom = item.getBoundingClientRect().bottom - document.querySelector('#iphone').getBoundingClientRect().top;
// Show the sub menu and move it to where the icon is
document.querySelector('.sub-menu').classList.add('show-sub-menu');
document.querySelector('.sub-menu').style.left = `${left}px`;
document.querySelector('.sub-menu').style.top = `${bottom - 16}px`;
}
}, 1000);
// If the user is still clicking after 2 seconds
timers.second = setTimeout(function() {
// Check they are clicking
if(pointerdown === true && helpers.checkPoint(mouseX, mouseY, 10)) {
// Now all icons should shake
document.body.setAttribute('data-shaking', true);
item.setAttribute('data-dragging', true);
// Hide the sub menu
document.querySelector('.sub-menu').classList.remove('show-sub-menu');
document.body.setAttribute('data-dropdown', false);
// Give each animation for shaking a delay, to give the appearance of randomness
timers.third = setTimeout(function() {
icons.forEach(function(i) {
i.style.animationDelay = `${offset}s`;
offset += 0.1;
})
}, 300);
}
}, 2000);
// If the icons are shaking, then the user may be trying to drag this particular icon. Set that icon
// to have a data-dragging of true. We can use this later
if(document.body.getAttribute('data-shaking') === "true") {
item.setAttribute('data-dragging', true);
}
});
// if the user lifts their mouse, then reset everything
item.addEventListener('pointerup', function() {
helpers.reset(false);
});
})
Suppression
Lorsque l’utilisateur clique sur une icône, nous devons alors animer cette icône…