Si vous êtes un tant soit peu comme moi, il y a fort à parier que votre communication en ligne est fortement axée sur le visuel. Mes collègues diraient plutôt que j’inonde les canaux Slacks et les merge requests avec des GIFs stupides, au lieu de m’exprimer avec des mots. Question de point de vue, mais la finalité reste la même : j’ai un besoin fréquent et récurrent d’accès à une collection d’images qui me servent au quotidien.
J’ai donc décidé de développer un petit outil pour me faciliter la vie (et pourrir celle de mes collègues), et comme je suis une personne aimable (sisi), j’ai également décidé d’en profiter pour vous donner un premier aperçu de Vue.js, un framework JavaScript front-end qui prend une place de plus en plus importante dans les projets de Synbioz. C’est parti !
L’outil susmentionné consistera en une simple page Web présentant les contenus sous forme de grille ; au clic sur l’un des éléments, son URL sera ajoutée au presse-papiers afin de permettre de la coller n’importe où immédiatement.
On pourrait imaginer d’autres fonctionnalités (tagging et recherche parmi les contenus, qui pourraient s’avérer utiles à mesure que la taille de la collection s’accroît), mais autant commencer par une version simple.
Pour les besoins de l’article, je vais tâcher de conserver une certaine simplicité dans la mise en place de l’outil. On aura donc un fichier HTML, un fichier JS et un fichier CSS — pour une application real-world, vous aurez sûrement plus de fichiers que ça.
Voici notre squelette de page HTML :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>MemeBox</title>
<link rel="stylesheet" href="app.css" />
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue"></script>
<script src="app.js"></script>
</body>
</html>
Notre collection d’images sera stockée en tant que tableau d’URLs, directement dans le code JavaScript. Je vous conseille d’en prévoir un certain nombre, histoire d’avoir un rendu un peu sympa ; si vous êtes en panne d’inspiration, vous pouvez toujours utiliser un service comme Placekitten.
const images = [
"http://path/to/some/image.gif",
"http://path/to/another/image.jpg",
// ...
];
On va également ajouter un peu de CSS pour partir sur de bonnes bases :
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
background-color: #333333;
font-family: sans-serif;
}
Première étape : afficher nos images ! On va envelopper chacune d’elles d’une
div
afin d’avoir plus de latitude niveau styles, et aussi d’anticiper la
fonctionnalité de survol/clic que nous mettrons en place par la suite. De par
la façon dont nous avons inclus Vue.js dans notre page, nous pouvons utiliser
son markup « custom » directement à l’intérieur de notre page HTML :
<div id="app" class="memebox">
<div
v-for="image in images"
class="meme"
>
<img :src="image" />
</div>
</div>
Ici, v-for
permet d’itérer sur notre collection ; l’élément sur lequel il se
trouve sera donc répété pour chacune de nos images. Quant au :
au début de
:src
, il spécifie que la valeur de l’attribut doit être interprétée en tant
que code JavaScript (il s’agit du nom de notre variable de boucle) plutôt que
comme une simple chaîne. Il s’agit de sucre pour la syntaxe complète, à savoir
v-bind:src="..."
.
Mais d’où vient la variable images
sur laquelle itère notre boucle ? Il nous
faut la définir en déclarant notre application côté JavaScript :
const images = [
// ...
];
new Vue({
el: "#app",
data: { images }
});
Nous en profitons pour indiquer au framework sur quel élément HTML « racine »
il doit travailler, à savoir notre div#app
.
Afin d’obtenir l’affichage en grille tel que souhaité, nous allons utiliser Flexbox côté CSS :
.memebox {
display: flex;
flex-flow: row wrap;
}
.meme {
flex: 1 0 10%;
min-width: 175px;
min-height: 175px;
}
.meme img {
max-width: 100%;
}
Et voilà ! En deux coups de cuiller à pot, nos images se rangent bien sagement sur une grille, responsive par-dessus le marché.
Le seul inconvénient est que nos images sont rarement carrées par défaut,
laissant ainsi apparaître des marges peu esthétiques autour de certaines
d’entre elles. Nous pourrions utiliser JavaScript pour recalculer leurs
dimensions à la volée, mais cela risquerait d’être inutilement coûteux ; nous
allons donc ruser en supprimant les balises img
et en utilisant nos images en
tant que backgrounds, ce qui nous donnera plus de liberté sur leur rendu :
<div id="app" class="memebox">
<div
v-for="image in images"
class="meme"
:style="{ backgroundImage: 'url(' + image + ')' }"
></div>
</div>
Notez la syntaxe spéciale supportée par Vue.js pour déclarer les styles
inline comme un objet JavaScript, et l’utilisation idoine de la camelCase
pour les clés.
Quelques modifications sont également nécessaires côté styles :
.meme {
flex: 1 0 10%;
position: relative;
min-width: 175px;
min-height: 175px;
background-position: center;
background-size: cover;
}
Maintenant que nos images s’affichent comme on le souhaite, voyons comment rendre tout ça « utile » !
On désire copier dans le presse-papiers l’URL d’une image lorsqu’on clique dessus ; on va commencer par voir si on peut l’afficher dans la console, en définissant une méthode sur notre application Vue.js :
new Vue({
el: "#app",
data: { images },
methods: {
copy(image) {
console.log(image);
}
}
});
Il ne nous reste plus qu’à ajouter le binding adéquat côté template :
<div id="app" class="memebox">
<div
v-for="image in images"
class="meme"
:style="{ backgroundImage: 'url(' + image + ')' }"
@click="copy(image)"
></div>
</div>
Les paramètres commençant par @
sont des event handlers ; une fois de plus,
il s’agit de sucre pour la syntaxe complète, à savoir v-on:click="..."
.
Désormais, lorsqu’on clique sur une de nos images, on voit bien son URL être
logguée en console. Afin de réellement mettre cette dernière dans le
presse-papiers, nous allons devoir utiliser une astuce qui consiste à créer
temporairement un élément textarea
. L’écriture de cette fonction n’est pas ce
qui nous intéresse ici, je vous la fournis donc :
function copyToClipboard(text) {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
Il suffit dès lors de l’utiliser en lieu et place de console.log
:
new Vue({
el: "#app",
data: { images },
methods: {
copy(image) {
copyToClipboard(image);
}
}
});
Notre (petite) application commence à prendre forme, et rend le service attendu. Nous allons terminer en la rendant graphiquement un peu plus sympathique à utiliser, en utilisant un état de survol :
.meme:before {
content: "Copy URL";
display: none;
position: absolute;
z-index: 1;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.75);
line-height: 175px;
text-align: center;
color: white;
cursor: pointer;
}
.meme:hover:before {
display: block;
}
Sympa, non ? Mais on peut faire encore mieux, en gérant une petite notion d’état sur notre application, afin de changer le texte après le clic (et de le restaurer lorsque la souris quitte l’image) :
new Vue({
el: "#app",
data: {
images,
clicked: false
},
methods: {
copy(image) {
copyToClipboard(image);
this.clicked = true;
},
hover() {
this.clicked = false;
}
}
});
Nous ajoutons donc une propriété clicked
valant initialement false
, passée
à true
lorsqu’on clique sur une image, et passée de nouveau à false
lors de
l’appel à notre nouvelle méthode hover
, qu’on va attacher à l’évènement
mouseleave
de nos images :
<div id="app" class="memebox">
<div
v-for="image in images"
class="meme"
:class="{ 'meme-clicked': clicked }"
:style="{ backgroundImage: 'url(' + image + ')' }"
@click="copy(image)"
@mouseleave="hover"
></div>
</div>
Notez l’utilisation conjointe de class
et :class
(respectivement statique
et dynamique), qui seront mixés par Vue.js à la compilation du template.
Notez également que le second utilise une syntaxe similaire à celle des styles
inline telle que vue plus haut. Un dernier coup de tournevis sur le CSS :
.meme-clicked:before {
content: "Copied!";
}
Le tour est joué ! Grâce à Vue.js, nous avons pu mettre sur pied cette petite application très rapidement et simplement, et j’espère vous avoir donné envie d’en apprendre plus sur ce framework, dont nous reparlerons très certainement à l’avenir. Stay tuned!
L’équipe Synbioz.
Libres d’être ensemble.