Förankra annoteringen till ett DOM-Range och placera sedan etiketten med hjälp av fragmentrektanglarna som området renderas i. Den här guiden visar en liten webbläsardemo för Mac-datorer som håller en anteckning fäst när en fras sträcker sig över infogade element och radbryts över flera rader.

Uppgiften är specifik: fäst en synlig annotering vid en fras i en vanlig webbsidelayout utan att återskapa texten. Du identifierar frasen, väljer ett fragment för etiketten och mäter på nytt efter layoutändringar. MDN dokumenterar Range.getClientRects() som webbläsarens API för att hämta rektanglarna som ett område upptar. Range: getClientRects() method.

Varför glider en etikett bort från radbruten text?

En fras har två relevanta representationer: dess position i dokumentet och dess aktuella geometri på skärmen. WHATWG definierar ett område med start- och slutpunkter, som var och en består av en nod och en förskjutning. Dessa ändpunkter kan ligga i olika textnoder, vilket gör att ett område kan sträcka sig över ett befintligt betoningselement. DOM Standard.

En enda begränsningsrektangel går miste om detaljer. Föreställ dig en fras som börjar mitt på en rad och slutar nära början av nästa. Dess omslutande rektangel inkluderar tomt utrymme bredvid båda raderna. En etikett centrerad på den rektangeln kan därför verka frikopplad från orden. Floating UI beskriver uttryckligen det här problemet för länkar som sträcker sig över flera rader och områdesmarkeringar. inline.

De enskilda rektanglarna behöver också tolkas. W3C anger att ett område kan returnera både markerade elementens kantlinjerektanglar och textrektanglar, i innehållets ordning. Nästlade element kan därför ge överlappande rektanglar; antalet rektanglar är inte ett tillförlitligt antal rader. Textens begränsningsrutor använder teckensnittsmått i stället för exakta konturer av de målade glyferna. CSSOM View Module.

Vilket fragment ska annoteringen förankras vid?

Välj en placeringsregel innan du skriver positioneringskod. Följande beslutstöd skiljer texten som annoteras från punkten där etiketten visas.

Avsett beteende Referensgeometri Placeringsregel
En anteckning som följer en fras Hela området Fäst den under det sista användbara fragmentet i en enkel horisontell layout från vänster till höger
En etikett nära en pekarinteraktion Områdets fragmentrektanglar Välj fragmentet vid interaktionens klientkoordinater
En centrerad översiktsetikett Den omslutande rektangeln Räkna med att etiketten kan hamna över tomt utrymme
En bakgrundsmarkering av hela frasen Hela området Använd en textmarkering oberoende av etikettens placering

Demon använder den första regeln. Den låser avsiktligt skrivriktningen och textflödet så att den sista rektangeln blir en användbar referenspunkt vid slutet. Se det som ett villkor för exemplet. W3C anger innehållets ordning för dessa rektanglar; specifikationen garanterar inte en universell ordning från det övre vänstra hörnet till det nedre högra. CSSOM View Module.

För pekarbaserad placering med Floating UI tar dess inline()-middleware emot viewportrelativa koordinaterna x och y. Dokumentationen begränsar det koordinatbaserade valet till åtskilda rektanglar, så det bör inte beskrivas som en obegränsad teckenväljare. inline.

Skapa en komplett demo med radbruten text på en Mac

Använd en aktuell skrivbordsversion av Firefox och öppna en tom flik på about:blank. Tryck på Command + Option + K för att öppna sidans webbkonsol. Mozilla dokumenterar det här kortkommandot och bekräftar att konsolen kör JavaScript i sidans kontext. Web Console.

Kör hela programmet nedan i den konsolen. Det skapar allt exempel-innehåll, all stil och alla kontroller, så du behöver inget projekt, inga importer och inget externt bibliotek. Exempelfrasen sträcker sig över ett befintligt betoningselement. Annoteringen är en etikett som endast visas; den har inga redigeringskontroller.

(() => {
  window.fragmentDemo?.destroy();
  const host = document.createElement('section');
  host.style.cssText = 'margin:48px 24px;padding-bottom:900px';
  const p = document.createElement('p');
  p.dir = 'ltr';
  p.style.cssText = 'font:20px/1.6 Georgia,serif;width:24rem;max-width:80vw;writing-mode:horizontal-tb';
  const a = document.createTextNode('Sample A: amber tiles follow the curved path ');
  const em = document.createElement('em');
  em.textContent = 'past the small blue circle and ';
  const c = document.createTextNode('toward the final silver square. End.');
  p.append(a, em, c);
  const button = document.createElement('button');
  button.textContent = 'Change text width';
  const status = document.createElement('p');
  host.append(button, p, status);
  document.body.append(host);
  const range = document.createRange();
  range.setStart(a, 'Sample A: '.length);
  range.setEnd(c, c.data.indexOf(' End.'));
  const note = document.createElement('div');
  note.textContent = 'Note A: review this phrase';
  note.style.cssText = 'position:fixed;z-index:1000;background:#fff2ad;color:#222;padding:6px 10px;font:14px/1.4 system-ui;pointer-events:none';
  document.body.append(note);
  const style = document.createElement('style');
  style.textContent = '::highlight(fragment-demo) {background-color:#dbeafe;}';
  document.head.append(style);
  const canHighlight = typeof Highlight !== 'undefined' && !!window.CSS?.highlights;
  if (canHighlight) CSS.highlights.set('fragment-demo', new Highlight(range));
  status.textContent = canHighlight ? 'The blue phrase owns Note A.' : 'Text highlighting unavailable; Note A still uses the range.';
  function update() {
    if (!host.isConnected) return;
    const rects = Array.from(range.getClientRects()).filter(r => r.width > 0 && r.height > 0);
    const r = rects.at(-1);
    note.hidden = !r || range.collapsed;
    if (note.hidden) return;
    const maxLeft = Math.max(8, innerWidth - note.offsetWidth - 8);
    note.style.left = Math.max(8, Math.min(r.left, maxLeft)) + 'px';
    note.style.top = (r.bottom + 8) + 'px';
  }
  let narrow = false;
  button.onclick = () => {
    narrow = !narrow;
    p.style.width = narrow ? '16rem' : '24rem';
    update();
  };
  const observer = new ResizeObserver(update);
  observer.observe(p);
  window.addEventListener('resize', update);
  window.addEventListener('scroll', update, true);
  document.fonts.ready.then(update);
  update();
  window.fragmentDemo = {destroy() {
    observer.disconnect();
    window.removeEventListener('resize', update);
    window.removeEventListener('scroll', update, true);
    if (canHighlight) CSS.highlights.delete('fragment-demo');
    host.remove(); note.remove(); style.remove();
  }};
})();

Ändpunkterna utesluter prefixet Sample A: och suffixet End.. Klicka på Change text width för att växla mellan två styckesbredder. Samma område förblir annoteringens mål medan etiketten placeras om under det sista användbara fragmentet. Rulla och ändra fönsterstorlek för att granska beteendet. Ta bort demon med window.fragmentDemo.destroy().

Programmet klarade en JavaScript-syntaxkontroll, och dess exempeloffsetar och beräkningar av fragmentpositioner klarade deterministiska kontroller. Webbläsarrendering kördes inte här; stegen ovan ger en visuell verifiering.

Hur bevarar demon textflöde och formning?

Området mäter den befintliga texten. När exempeltexten har skapats låter programmet frasens textnoder och betoningselement ligga kvar. Det lägger till etiketten separat och kan dessutom måla frasen med CSS Custom Highlight API, som MDN dokumenterar som en funktion som fungerar utan att ändra den underliggande DOM-strukturen. CSS custom highlight API.

Den skillnaden är viktig när du anpassar metoden. Om du lägger till utfyllnad, kantlinjer eller marginaler mellan teckenenheter kan det uppstå formningsgränser. W3C anger också icke-inledande vertical-align och gränser för dubbelriktad isolering som villkor som bryter formningen. Undvik att infoga individuellt positionerade teckenomslag bara för att få koordinater; mät i stället webbläsarens befintliga resultat. CSS Text Module Level 3, Shaping Across Element Boundaries.

För faktisk programtext ska du härleda områdets ändpunkter från de avsedda textgränserna. DOM-textoffsetar använder kodenheter; de är inte antal synliga glyfer. Se till att ändpunkterna ligger i linje med hela avsedda tecken i stället för att anta att varje visat tecken motsvarar en offset. DOM Standard.

Markering är valfri. MDN listar Highlight som Baseline 2025 och påpekar att äldre webbläsare kanske saknar stöd. Funktionskontrollen i demon bevarar annoteringens placering när markering inte är tillgänglig och visar ett förklarande statusmeddelande. Highlight.

Vad måste uppdateras när sidan flödas om?

Demon mäter på nytt när stycket ändrar storlek, när fönstret ändrar storlek och vid rullning. ResizeObserver upptäcker ändringar i elementens mått, även sådana som inte beror på att webbläsarfönstret ändrar storlek. Callback-funktionen flyttar här bara den separata etiketten, vilket undviker en återkopplingsloop där det uppmätta styckets storlek ändras gång på gång. ResizeObserver.

Teckensnittsladdning kräver en egen uppdatering. Programmet mäter omedelbart och mäter sedan igen när document.fonts.ready löser sig. MDN anger att det här löftet löses när dokumentets inläsning av teckensnitt och tillhörande layoutåtgärder har slutförts. Senare programstyrda ändringar av teckensnitt eller innehåll bör uttryckligen begära en ny mätning. FontFaceSet: ready property.

Håll koordinaterna i ett och samma system. Demon använder en etikett med fast position på en tom sida och tillämpar klientkoordinater direkt. Om du lägger till dokumentets rullningsförskjutningar omvandlas koordinaterna till dokumentkoordinater. MDN förklarar att klientrektanglar är viewportrelativa och att tillägg av rullningsförskjutningar är konverteringen som används när dokumentrelativa koordinater önskas. Element: getBoundingClientRect() method.

Det här lilla exemplet begränsar placeringen horisontellt men hanterar inte kollisionsundvikande vertikalt, dold visning utanför skärmen eller godtyckliga transformerade containrar. Det återställer inte heller en annotering efter en omladdning eller ett fullständigt utbyte av texten. I en applikation bör du behålla ett sätt att identifiera det avsedda textstycket, återskapa dess område efter renderingen och sedan mäta igen.

När bör du använda Floating UI?

Använd Floating UI när etiketten behöver placering som tar hänsyn till beskärning och en hanterad uppdateringslivscykel. Dess dokumenterade gränssnitt för virtuella referenser stöder ett område via två metoder: getBoundingClientRect(), som returnerar områdets omslutande rektangel, och getClientRects(), som returnerar dess fragmentrektanglar. Ange contextElement med ett riktigt element som innehåller texten, så att beskärnings- och uppdateringsdetekteringen får ett element som kontext. Virtual Elements.

Skicka den virtuella referensen till computePosition() och lägg till inline() nära början av middleware-arrayen, före flip(). Anpassa det flytande elementets CSS-positionering efter den valda strategin. Dokumentationen beskriver flip() för att byta sida när utrymmet inte räcker och shift() för att flytta en etikett inom dess beskärningsgränser. inline, computePosition.

Starta autoUpdate() bara medan annoteringen är monterad och anropa den returnerade rensningsfunktionen när etiketten tas bort. De dokumenterade uppdateringsmekanismerna omfattar rullning av överordnade element, storleksändringar och layoutförskjutningar. Utlös ändå en uppdatering efter en programändring som påverkar det valda textfragmentet utan att ändra måtten på den observerade containern. autoUpdate.

Felsök en annotering som fortfarande ser fel ut

Gå igenom dessa kontroller i ordning, så att ett geometriproblem inte döljer ett problem med textmålet.

  1. Bekräfta frasen. Granska range.toString() och kontrollera båda ändpunkterna innan du justerar koordinaterna.
  2. Granska fragmenten. Logga Array.from(range.getClientRects()). Flera överlappande poster kan återspegla nästlat infogat innehåll; tolka inte antalet som ett antal rader.
  3. Kontrollera placeringsregeln. En omslutande rektangel, det sista fragmentet och ett fragment valt med pekaren passar för olika annoteringsbeteenden.
  4. Kontrollera koordinatsystemet. För demon på den tomma sidan ska den fasta etiketten använda klientkoordinater.
  5. Upprepa efter layoutändringar. Ändra bredden, rulla, ändra storlek och låt teckensnitten bli färdigladdade. Återskapa området om applikationen ersätter textnoderna.
  6. Kontrollera layoutantagandena. För vertikalt textflöde, blandade riktningar, spalter eller transformerade containrar ska du definiera och testa en lämplig regel för val av fragment innan du använder demon genväg för det sista fragmentet igen.

En tillförlitlig annotering börjar med ett korrekt textområde och en uttrycklig placeringsregel. Låt webbläsaren sköta radbrytning och formning och mät sedan de renderade fragmenten igen när den relevanta layouten ändras. Då behåller anteckningen en stabil relation till frasen samtidigt som orden kan flöda naturligt.

Källor