2
votes

Comment utiliser JS pour détecter les mots "orphelins" sur une ligne par eux-mêmes à la fin d'un div à largeur fixe?

Je ne suis pas sûr que "orphelin" soit approprié, mais c'est le mieux que je puisse trouver ici.

Je veux être capable de détecter quand il y a un mot ou deux supplémentaires dans la dernière ligne à la fin du texte dans un div.

REMARQUE: je n'essaie pas d'éliminer ces "orphelins" en leur donnant la compagnie d'un autre mot sur la dernière ligne.

J'ai obtenu un div avec une largeur fixe qui contient du texte dont j'ai besoin pour terminer uniformément à la fin d'une ligne. Parfois, un mot (ou deux) se glisse jusqu'à une ligne supplémentaire. J'aimerais pouvoir détecter quand cela se produit (afin que le code puisse ensuite ajuster la taille de la police du div jusqu'à ce que le / les mots orphelins apparaissent à la ligne précédente).

Utilisation de text-align-last: justify; n'est pas une solution à mon problème en soi.

J'ai essayé element.getClientRects () , pensant que je obtiendrait un rectangle pour chaque ligne, puis regarderait simplement la largeur du rectangle le plus bas donné. Mais pour un seul div, je n'obtiens qu'un seul rectangle. Et si je mets le texte dans une plage à l'intérieur du div, j'obtiens parfois jusqu'à neuf rectangles pour six lignes de texte dans la plage. Yikes! Je n'ai aucune idée de ce que représentent ces rectangles.

Quelqu'un sait comment je peux détecter un mot isolé supplémentaire sur une ligne par lui-même à la fin du texte multiligne dans un div de largeur fixe? p>


Ajout

ATTENTION: le nombre optimal de lignes n'est pas connu à l'avance; cela peut être compris entre une et cinq lignes.

On m'a demandé de donner des exemples.

L'exemple 1 montre trois lignes, mais devrait être de deux lignes: L'exemple 1 inclut un retrait

DOMRectList {0: DOMRect, 1: DOMRect, 2: DOMRect, 3: DOMRect, 4: DOMRect, length: 5}
0: DOMRect {x: 123, y: 319.875, width: 375.5, height: 22, top: 319.875, …}
1: DOMRect {x: 498.5, y: 319.875, width: 70.203125, height: 22, top: 319.875, …}
2: DOMRect {x: 123, y: 343.875, width: 82.40625, height: 22, top: 343.875, …}
3: DOMRect {x: 205.40625, y: 343.875, width: 363.46875, height: 22, top: 343.875, …}
4: DOMRect {x: 123, y: 367.875, width: 53.578125, height: 22, top: 367.875, …}
length: 5
__proto__: DOMRectList

$ 0.getClientRects () sur le span renvoie:

<div class="chunk">
<span> οὔ)· ἄνθρωπος γὰρ ἵππου ἕτερον τῷ εἴδει καὶ
 τἀναντία ἀλλήλων. 
 καὶ πρὸς Παρμενίδην δὲ ὁ αὐτὸς τρόπος τῶν λόγων,
 <div class="bekkerLine">22</div>
</span></div>

L'exemple 2 se présente également sous forme de trois lignes et de nouveau devrait être de deux lignes: entrez la description de l'image ici

DOMRectList {0: DOMRect, 1: DOMRect, 2: DOMRect, 3: DOMRect, length: 4}
0: DOMRect {x: 475.796875, y: 328.875, width: 60.609375, height: 22, top: 328.875, …}
1: DOMRect {x: 536.40625, y: 328.875, width: 48.21875, height: 22, top: 328.875, …}
2: DOMRect {x: 139, y: 352.875, width: 445.546875, height: 22, top: 352.875, …}
3: DOMRect {x: 139, y: 376.875, width: 65.109375, height: 22, top: 376.875, …}
length: 4
__proto__: DOMRectList

$ 0.getClientRects () sur le span renvoie:

<div class="chunk" style="text-indent:45ex;">
<div class="bekkerLine">25</div>
<span>τὸ μὲν οὖν εἰ ἓν καὶ ἀκίνητον τὸ ὂν σκοπεῖν οὐ περὶ φύσεος ἐστι σκοπεῖν·</span>
</div>

Exemple 3 Je ne voudrais pas "réparer" car la dernière ligne contient trop de mots: entrez la description de l'image ici


1 commentaires

Pouvez-vous ajouter un extrait de code avec un exemple?


3 Réponses :


1
votes

MISE À JOUR : la solution détecte désormais s'il y a THRESHOLD nombre de mots sur la dernière ligne. Ici, je règle THRESHOLD sur 2 . Pour le deuxième div, il se termine par trois mots sur la dernière ligne et ne change pas. Pour le troisième div, il se termine par un mot sur la dernière ligne et se réduit. Dans le quatrième div, il commence par deux mots sur la dernière ligne et se réduit.

<div class="fixed">Text Text Text Text Text Text</div>
<div class="fixed">Text Text Text Text Text Text Text Text Text</div>
<div class="fixed">Text Text Text Text Text Text Text Text Text Text Text Text Text</div>
<div class="fixed">Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text</div>
div {
  width: 200px;
  padding: 10px;
  font-size: 16px;
}
const THRESHOLD = 2;

document.querySelectorAll("div.fixed").forEach(el => {
  const { height: originalHeight } = el.getClientRects()[0];
  let thresholdIndex;
  for (let i = el.innerHTML.length, occurences = 0; i > 0; i--) {
    if (el.innerHTML[i] === " ") {
      occurences++;
    }
    if (occurences === THRESHOLD) {
      thresholdIndex = i;
      break;
    }
  }

  if (thresholdIndex) {
    const lastWords = el.innerHTML.substring(
      thresholdIndex,
      el.innerHTML.length
    );
    const text = el.innerHTML.substring(0, thresholdIndex);
    el.innerHTML = text;
    const { height: newHeight } = el.getClientRects()[0];
    el.innerHTML += lastWords;
    if (newHeight < originalHeight) {
      let height = originalHeight;
      while (height > newHeight) {
        const currentFontSize = parseInt(
          window.getComputedStyle(el).getPropertyValue("font-size"),
          10
        );
        const newFontSize = currentFontSize - 1;
        el.style.fontSize = `${newFontSize}px`;
        height = el.getClientRects()[0].height;
      }
    }
  }
});


9 commentaires

Vous semblez changer la taille de la police. Je demande détecter s'il y a un mot supplémentaire sur la dernière ligne. De plus, ces div peuvent être n'importe quel nombre de lignes, jusqu'à cinq de haut.


pour que le code puisse ensuite ajuster la taille de la police du div jusqu'à ce que le ou les mots orphelins apparaissent à la ligne précédente Qu'entendez-vous par là? Vous ne souhaitez pas modifier la taille de la police?


Cette solution est indépendante du nombre de lignes (nombre de div.fixed c'est-à-dire)


J'apprécie votre réponse, mais je ne pose aucune question à ce sujet. C'est pourquoi c'est entre parenthèses. Je sais très bien comment faire cette partie. Je demande la partie qui n'est pas entre parenthèses. Merci d'avoir essayé!


Notez qu'un autre cas serait si vous aviez suffisamment de texte pour atteindre presque deux lignes entières. Votre routine réduirait-elle la taille de la police jusqu'à ce que les deux lignes tiennent sur une seule? Peut-être ai-je mal lu, mais il semblerait que oui.


Oui, cette solution ajuste la taille de la police jusqu'à ce que le div.fixed soit une ligne. Je pense que vous devez ajouter un exemple à votre question pour clarifier ce que vous essayez d'accomplir.


Croyez-le ou non, il faut un temps limité pour donner des exemples. Je l'ai maintenant fait, merci.


Je ne sais pas trop sur quoi vous essayez d'en venir avec ce commentaire 🙃


@JohnK s'est adapté aux informations supplémentaires que vous avez fournies



1
votes

J'ai trouvé une sorte de réponse à votre question sur un autre site:

"enroulez chaque caractère dans un puis parcourez ces code > s, en vérifiant la valeur offsetTop pour chacun. ".

Dans votre cas, vous ne vous souciez que de savoir si le dernier troisième élément est au-dessus du dernier puisque vous voulez savoir si les deux derniers mots sont suspendus. p >

J'ai fait ce petit extrait de code pour montrer comment cela fonctionne: (passer de 208 à 209 devrait produire un changement)

<input id="width" type="number" onchange="handleWidth()"/>

<p id="paragraph">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 
culpa qui officia deserunt mollit 
anim <span id="third-last">id</span> 
est <span id="last">laborum.</span>
</p>
var paragraph = document.getElementById("paragraph");
var thirdLast = document.getElementById("third-last");
var last = document.getElementById("last");
var widthSelector = document.getElementById("width");

function handleWidth(){
  var fontSize = 16;
  paragraph.style.fontSize = fontSize + "px";
  var width = widthSelector.value;
  paragraph.style.width = width + "px";
  while(thirdLast.offsetTop < last.offsetTop && fontSize > 0){
    fontSize--;
    paragraph.style.fontSize = fontSize + "px";
  }
}


3 commentaires

Assez intelligent, et presque ce dont j'ai besoin. Je vous remercie!


Un problème restant ici est de savoir comment créer automatiquement les travées internes. La réponse actuelle de @ Mohm fait cela, bien qu'imparfaitement.


@JohnK Vous pouvez obtenir le innerText du paragraphe et le diviser par des espaces, puis modifier le troisième dernier et dernier mots en les entourant de travées comme ci-dessus et remplacer le innerHtml du paragraphe par ce texte modifié. Je peux mettre à jour ma réponse si cela ressemble à ce que vous recherchez



0
votes

Les deux solutions publiées ne correspondaient pas tout à fait à ce dont j'avais besoin. (La façon dont j'ai écrit peut être en partie à blâmer. En tout cas, j'ai voté chacun d'entre eux.) J'ai fini par écrire ma propre méthode pour calculer le nombre de lignes que le div devrait prendre.

J'ai mis un span dans le div. Cette méthode additionne la largeur des rects client à chaque position y afin d'obtenir la longueur de chaque ligne de la travée (je ne sais pas pourquoi elles sont divisées en premier lieu, mais elles le sont!). Il compte ensuite le nombre de ces rects qui sont inférieurs à la moitié de la largeur du div et le renvoie comme le nombre optimal de lignes.

howManyLinesIsBest(divEl) {
    const rect0 = (divEl.getClientRects())[0];
    const width = rect0.width;
    const span = (divEl.getElementsByTagName("span"))[0];
    const lines = {};
    [].forEach.call(span.getClientRects(),
      v => {
        if (lines.hasOwnProperty(v.y)) {
          lines[v.y] += v.width;
          return;
        }
        lines[v.y] = v.width;
      }
    );
    const wideRects = Object.values(lines).filter(v => v > width/2);
    return wideRects.length > 1 ? wideRects.length : 1;
  }

Cela fonctionne plutôt bien! p>


1 commentaires

Après avoir publié cela, j'ai remarqué un comportement étrange: lorsque vous donnez à la bordure de la travée une largeur finie, les rects client se rejoignent pour que le nombre de rects corresponde au nombre de lignes! Et ils restent ainsi même après avoir désactivé la frontière. Néanmoins, la solution publiée est robuste face à ce changement.