Migrer un site Framer vers du code, mesuré plutôt qu’estimé à l’œil

24 août 20269 min de lecture

Framer n’exporte pas de code. Ni une version dégradée, ni un dump HTML qu’on nettoierait : le balisage publié est généré, et inutilisable comme point de départ. Migrer un site Framer vers du code n’est donc pas une conversion, c’est une reconstruction. Le design system doit être relevé sur le site en marche.

Je l’ai fait pour mon propre portfolio, sept pages, et je suis arrivé à l’exactitude au pixel en desktop, hauteur totale du document comprise. La méthode est simple. Les pièges ne le sont pas, et trois d’entre eux m’ont coûté des heures.

Les images sont récupérables, en mieux que ce que le site sert

Première bonne surprise : Framer sert des images réduites. Chaque <img> pointe vers framerusercontent.com/images/<id>.png?scale-down-to=1024 ou 2048. Retirez la chaîne de requête et vous obtenez le fichier d’origine.

Dans mon cas, cela a transformé six visuels de 479px de large en originaux allant jusqu’à 7040 par 5280. Le site reconstruit est plus net que celui qu’il remplace, ce qui n’est pas le sens habituel d’une migration.

Relevez les sources des <img> dans le DOM, enlevez les paramètres, et gardez un manifeste qui associe chaque identifiant Framer à un nom de fichier lisible. Vous voudrez relancer ce téléchargement plus tard : faites-en un script plutôt qu’une séance de copier-coller.

Relevez le design system, ne l’estimez pas à l’œil

Ouvrez le site et, plutôt que de le scruter, demandez au navigateur ce qu’il fait. Parcourez le DOM et collectez, pour chaque élément qui porte du texte ou une image : sa boîte englobante, la famille de police, la taille, la graisse, l’interligne, l’interlettrage, la transformation de casse, la couleur.

Ce seul passage donne l’échelle typographique, la palette, la largeur du conteneur et la grille. Répétez-le à trois largeurs de fenêtre et vous avez aussi les points de rupture. Les miens sont ressortis en téléphone sous 810, tablette de 810 à 1199, desktop à partir de 1200, avec le conteneur à 1140 centré en desktop et pleine largeur moins un padding fixe en dessous.

C’est nettement plus fiable que de lire des valeurs sur des captures, et cela prend quelques minutes.

Piège numéro un : vous mesurez des animations, pas une mise en page

C’est celui qui m’a coûté le plus de temps, et il est invisible si on ne le cherche pas.

Les animations d’apparition de Framer démarrent les éléments décalés et les posent quand ils entrent dans la fenêtre. Dans un navigateur headless, ces animations ne se déclenchent souvent jamais. Toute position mesurée inclut donc le décalage initial, et ce décalage n’est pas uniforme : chez moi 80px sur le titre de page, 40px sur les blocs média, 20px ailleurs.

J’ai construit une première version qui correspondait exactement à mes mesures, et qui était donc fausse partout : j’avais reproduit fidèlement l’état animé, avec de vraies marges, et compensé l’erreur par d’autres constantes qui s’annulaient par chance.

Neutralisez chaque transformation avant de mesurer quoi que ce soit :

for (const el of document.querySelectorAll('*')) {
  const cs = getComputedStyle(el)
  if (cs.transform !== 'none') el.style.setProperty('transform', 'none', 'important')
  if (el.hasAttribute('data-framer-appear-id') && parseFloat(cs.opacity) < 1) {
    el.style.setProperty('opacity', '1', 'important')
  }
}

Une règle de feuille de style ne suffit pas, parce que Framer écrit les transformations en styles en ligne. Il faut parcourir les éléments.

Piège numéro deux : un interlettrage en em s’hérite en longueur absolue

Framer exprimait l’interlettrage en -0.04em à toutes les tailles, ce qui est propre. Je l’ai déclaré une fois sur body et je suis passé à autre chose.

C’est faux, et le CSS se comporte correctement. Une longueur en em se résout par rapport à la taille de police de l’élément qui la déclare, puis s’hérite comme cette longueur absolue déjà résolue. Déclaré sur un body à 18px, -0.04em devient -0.72px, et le titre de 70px hérite de -0.72px au lieu des -2.8px qu’il lui faut.

Le titre était visiblement trop espacé et j’ai passé un moment à accuser la police. Chaque classe qui change la taille de police doit redéclarer l’interlettrage.

Piège numéro trois : Framer pose white-space: pre

Le texte riche de Framer se rend avec white-space: pre ou pre-wrap, parce que les retours à la ligne sont écrits, pas calculés. Deux conséquences.

Le texte que vous attendez à la ligne ne passe pas à la ligne. Sur mon site, un tableau à deux colonnes d’intitulés de diplômes débordait de sa colonne au lieu de se couper, et l’original coupait simplement le débordement par un overflow: hidden sur un ancêtre. Reproduire la mise en page voulait dire reproduire le non-retour à la ligne et la coupe, ce qui était désagréable à écrire et juste.

Et cela signifie qu’on ne peut pas déduire une largeur de conteneur de l’endroit où le texte se coupe, puisqu’il ne se coupe pas.

Piège numéro quatre : les valeurs qui ne redescendent jamais

Les écarts résiduels sur mes pages projet, aux largeurs étroites, ressemblaient à un problème de métriques de police. J’ai comparé les versions variable et statique de la fonte, cherché du côté du kerning, et je n’ai rien trouvé.

La vraie cause était banale. J’avais transcrit le paragraphe de résultats en max-width: 855px, mesuré en desktop. Dans l’original, c’est 75 pour cent du conteneur : 855px à 1140, 578 en tablette, 270 en téléphone. Une valeur figée en pixels est juste à exactement une largeur de fenêtre, et fausse à toutes les autres.

Même histoire pour une taille de police que j’avais fixée à 18px quand l’original descendait à 16 en téléphone, et pour une gouttière de grille qui passe de 20px à 10.

Si une valeur vous paraît suspicieusement ronde à votre largeur de référence, mesurez- la aux deux autres avant de l’écrire.

Vérifiez avec un invariant, pas avec vos yeux

Des captures côte à côte ne vous diront pas si vous avez 20px d’écart sur une page de 9000. La hauteur totale du document, si, et elle a une propriété utile : c’est une mesure de mise en page, donc les transformations d’animation ne l’affectent pas.

Chargez les deux sites dans des iframes de même origine à chaque point de rupture, laissez les animations se poser, et comparez document.documentElement.scrollHeight. Quand la hauteur correspond et que les positions des médias correspondent, c’est fini. Quand la hauteur correspond mais pas les positions, vous avez deux erreurs qui s’annulent, ce qui est exactement l’état où j’étais après le premier piège.

Vingt et une combinaisons de page et de point de rupture, dix-huit exactes, trois à 17 pixels près. Les trois restantes sont des différences de coupe d’une seule ligne dans un bloc à deux colonnes à 390px, et je me suis arrêté là volontairement.

Ce qu’il ne faut pas poursuivre

Une partie de ce que vous mesurez n’est pas du design, c’est du résidu. Mon original portait une ligne vide finale dans un bloc de texte riche, qui ajoutait 19,2px dont dépendait toute la page. Un autre bloc faisait 42px de plus que son contenu, pour la même raison. Le bloc contact utilisait 151px de padding haut sur la page d’accueil et 131 sur les pages projet, sans que personne ne l’ait décidé.

Reproduisez-les là où la géométrie en aval en dépend, commentez pourquoi, et n’essayez pas de les rationaliser en système. Ce n’est pas un système. C’est ce qu’un designer a poussé un mardi.

Et une fois la copie fidèle, arrêtez d’être fidèle. Une reproduction au pixel d’un site de deux ans est un plancher, pas une ligne d’arrivée. La première chose que j’ai faite après l’avoir égalé a été d’ajouter des légendes sous chaque image, ce qui a changé les hauteurs de page et cassé la propriété même que je venais de passer une journée à obtenir. C’était le but.

← tous les écrits

Contact

Envie de donner corps à un projet, ou simplement d’en parler ? Écrivez-moi, on fera quelque chose qui reste.

Je suis là pour écouter, travailler avec vous, et trouver des réponses de design qui tiennent.