Ancle la anotación a un rango DOM Range, luego posicione su etiqueta usando los rectángulos de fragmento renderizados del rango. Esta guía muestra una pequeña demostración en navegador de escritorio Mac que mantiene una nota adjunta cuando una frase cruza elementos en línea y se ajusta en varias líneas.
La tarea es específica: adjuntar una anotación visible a una frase en un diseño de página web ordinario sin reconstruir su texto. Identificará la frase, elegirá un fragmento para la etiqueta y volverá a medir después de los cambios de diseño. MDN documenta Range.getClientRects() como la API del navegador para recuperar los rectángulos ocupados por un rango. Método getClientRects() de Range.
¿Por qué una etiqueta se aleja del texto ajustado?
Una frase tiene dos representaciones relevantes: su posición en el documento y su geometría actual en pantalla. WHATWG define un rango a través de puntos de inicio y fin, cada uno consistente en un nodo y un desplazamiento. Esos extremos pueden estar en diferentes nodos de texto, permitiendo que un rango cruce un elemento de énfasis existente. Estándar DOM.
Un solo rectángulo delimitador pierde detalle. Imagine una frase que comienza a mitad de una línea y termina cerca del inicio de la siguiente. Su rectángulo envolvente incluye espacio vacío junto a ambas líneas. Una etiqueta centrada en ese rectángulo puede parecer, por tanto, desconectada de las palabras. Floating UI identifica explícitamente este problema para enlaces multilínea y selecciones de rango. en línea.
Los rectángulos individuales también necesitan interpretación. W3C especifica que un rango puede devolver tanto rectángulos de borde de elementos seleccionados como rectángulos de texto, en orden de contenido. Los elementos anidados pueden producir, por tanto, rectángulos superpuestos; la cantidad de rectángulos no es un recuento de líneas fiable. Los límites de texto usan métricas de fuente en lugar de contornos exactos de glifos pintados. Módulo CSSOM View.
¿Qué fragmento debe ser dueño de la anotación?
Elija una regla de colocación antes de escribir código de posicionamiento. La siguiente ayuda de decisión separa el texto anotado del punto donde aparece su etiqueta.
| Comportamiento previsto | Geometría de referencia | Regla de colocación |
|---|---|---|
| Una nota tras una frase | El rango completo | Adjunte debajo de su fragmento final usable en un diseño horizontal simple, de izquierda a derecha |
| Una etiqueta cerca de una interacción de puntero | Los rectángulos de fragmento del rango | Elija el fragmento en las coordenadas de cliente de la interacción |
| Una etiqueta de visión general centrada | El rectángulo envolvente | Acepte que la etiqueta pueda quedar sobre espacio vacío |
| Un marcado de fondo para toda la frase | El rango completo | Use un resaltado de texto independientemente de la colocación de la etiqueta |
La demostración usa la primera regla. Fija deliberadamente el modo de escritura y la dirección para que el rectángulo final sea una referencia de punto final útil. Trate eso como una condición del ejemplo. W3C especifica orden de contenido para estos rectángulos; no promete un orden universal desde la esquina superior izquierda a la inferior derecha. Módulo CSSOM View.
Para colocación basada en puntero con Floating UI, su middleware inline() acepta coordenadas x y y relativas al viewport. La documentación limita esa elección basada en coordenadas a rectángulos disjuntos, por lo que no debe describirse como un selector de caracteres sin restricciones. en línea.
Construya una demostración completa de texto ajustado en un Mac
Use una versión actual de Firefox de escritorio y abra una pestaña en blanco en about:blank. Presione Comando + Opción + K para abrir la Consola Web de la página. Mozilla documenta este atajo y confirma que la consola ejecuta JavaScript en el contexto de la página. Consola Web.
Ejecute el programa completo a continuación en esa consola. Crea todo su contenido de muestra, estilos y controles, por lo que no se requiere proyecto, importaciones ni biblioteca externa. La frase sintética cruza un elemento de énfasis existente. La anotación es una etiqueta de solo visualización; no tiene controles de edición.
(() => {
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();
}};
})();
Los extremos excluyen el prefijo Sample A: y el sufijo End.. Haga clic en Cambiar ancho de texto para alternar entre dos anchos de párrafo. El mismo rango sigue siendo el objetivo de la anotación mientras la etiqueta se reposiciona debajo de su fragmento final usable. Desplácese y cambie el tamaño de la ventana para inspeccionar el comportamiento. Elimine la demostración con window.fragmentDemo.destroy().
El programa pasó una verificación de sintaxis de JavaScript, y sus desplazamientos de muestra y aritmética de posición de fragmento pasaron comprobaciones deterministas. El renderizado del navegador no se ejecutó aquí; los pasos anteriores proporcionan la verificación visual.
¿Cómo preserva la demostración el flujo y la formación del texto?
El rango mide el texto existente. El programa deja los nodos de texto de la frase y el elemento de énfasis en su lugar después de construir la muestra. Añade la etiqueta por separado y opcionalmente pinta la frase a través de la API CSS Custom Highlight, que MDN documenta como funcional sin cambiar la estructura DOM subyacente. API CSS custom highlight.
Esa distinción importa al adaptar el enfoque. Añadir relleno, bordes o márgenes en línea entre unidades de carácter puede introducir límites de formación. W3C también identifica vertical-align no iniciales y límites de aislamiento bidireccional como condiciones que rompen la formación. Evite insertar envoltorios de carácter posicionados individualmente meramente para obtener coordenadas; mida el resultado existente del navegador. Módulo CSS Text Nivel 3, Formación a través de límites de elementos.
Para texto de aplicación real, derive los extremos del rango de los límites de texto previstos. Los desplazamientos de texto DOM usan unidades de código; no son recuentos de glifos visibles. Mantenga los extremos alineados con caracteres completos previstos en lugar de asumir que cada carácter mostrado ocupa un desplazamiento. Estándar DOM.
El resaltado es opcional. MDN lista Highlight como Baseline 2025 y señala que navegadores más antiguos pueden carecer de él. La comprobación de característica en la demostración preserva la colocación de la anotación cuando el resaltado no está disponible y muestra un mensaje de estado explicativo. Highlight.
¿Qué debe actualizarse cuando la página refluya?
La demostración vuelve a medir al cambiar el tamaño del párrafo, al cambiar el tamaño de la ventana y al desplazar. ResizeObserver detecta cambios en las dimensiones de elementos, incluidos cambios no relacionados con el cambio de tamaño de la ventana del navegador. Su callback aquí mueve solo la etiqueta separada, evitando un bucle de retroalimentación que cambie repetidamente el tamaño del párrafo medido. ResizeObserver.
La carga de fuentes merece su propia actualización. El programa mide inmediatamente, luego mide de nuevo cuando document.fonts.ready se resuelve. MDN indica que esta promesa se resuelve después de que la carga de fuentes del documento y las operaciones de diseño asociadas se completen. Cambios posteriores de fuentes o contenido impulsados por la aplicación deben solicitar explícitamente otra medición. Propiedad ready de FontFaceSet.
Mantenga las coordenadas en un solo sistema. La demostración usa una etiqueta de posición fija en una página en blanco y aplica coordenadas de cliente directamente. Añadir desplazamientos de scroll del documento los cambiaría a coordenadas de documento. MDN explica que los rectángulos de cliente son relativos al viewport y que añadir desplazamientos de scroll es la conversión usada cuando se desean coordenadas relativas al documento. Método getBoundingClientRect() de Element.
Este pequeño ejemplo limita la colocación horizontal pero no implementa evitación de colisiones verticales, ocultamiento fuera de pantalla ni contenedores transformados arbitrarios. Tampoco restaura una anotación después de una recarga o un reemplazo completo del texto. Para una aplicación, conserve una forma de identificar el pasaje previsto, reconstruya su rango después del renderizado y luego mida de nuevo.
¿Cuándo debería usar Floating UI?
Use Floating UI cuando la etiqueta necesite colocación consciente de recorte y un ciclo de vida de actualización gestionado. Su interfaz virtual documentada soporta un rango a través de dos métodos: getBoundingClientRect() devolviendo el rectángulo envolvente del rango y getClientRects() devolviendo sus rectángulos de fragmento. Proporcione contextElement con un elemento real que contenga el texto para que el recorte y la detección de actualizaciones tengan un contexto de elemento. Elementos Virtuales.
Pase esa referencia virtual a computePosition() e incluya inline() cerca del principio del array de middleware, antes de flip(). Haga coincidir el posicionamiento CSS del elemento flotante con la estrategia seleccionada. La documentación describe flip() para cambiar de lado cuando el espacio es insuficiente y shift() para mover una etiqueta dentro de sus límites de recorte. en línea, computePosition.
Inicie autoUpdate() solo mientras la anotación esté montada, y llame a su función de limpieza devuelta al eliminar la etiqueta. Sus mecanismos de actualización documentados cubren scroll de ancestros, cambio de tamaño y cambios de diseño. Aún así, dispare una actualización después de un cambio de aplicación que altere los fragmentos del texto elegido sin cambiar las dimensiones del contenedor observado. autoUpdate.
Diagnostique una anotación que aún se ve mal
Recorra estas comprobaciones en orden para que un problema de geometría no disimule un problema de objetivo de texto.
- Confirme la frase. Inspeccione
range.toString()y verifique ambos extremos antes de ajustar coordenadas. - Inspeccione los fragmentos. Registre
Array.from(range.getClientRects()). Varias entradas superpuestas pueden reflejar contenido en línea anidado; no interprete la cantidad como recuento de líneas. - Verifique la regla de colocación. Un rectángulo envolvente, fragmento final y fragmento seleccionado por puntero sirven a diferentes comportamientos de anotación.
- Verifique el sistema de coordenadas. Para la demostración de página en blanco, mantenga la etiqueta fija en coordenadas de cliente.
- Repita después de cambios de diseño. Cambie ancho, desplace, redimensione y deje que las fuentes terminen de cargar. Reconstruya el rango si la aplicación reemplaza sus nodos de texto.
- Verifique los supuestos de diseño. Para escritura vertical, direcciones mixtas, columnas o contenedores transformados, defina y pruebe una regla de selección de fragmento apropiada antes de reutilizar el atajo de fragmento final de la demostración.
Una anotación fiable comienza con un rango de texto preciso y una regla de colocación explícita. Mantenga al navegador responsable del ajuste y la formación, luego vuelva a medir sus fragmentos renderizados cada vez que cambie el diseño relevante. Eso da a la nota una relación estable con la frase mientras permite que las palabras fluyan naturalmente.
Fuentes
- Estándar DOM — WHATWG
- Módulo CSSOM View — W3C
- Módulo CSS Text Nivel 3 — W3C
- Método getClientRects() de Range — MDN
- Método getBoundingClientRect() de Element — MDN
- API CSS custom highlight — MDN
- Highlight — MDN
- ResizeObserver — MDN
- Propiedad ready de FontFaceSet — MDN
- Consola Web — Mozilla
- en línea — Floating UI
- Elementos Virtuales — Floating UI
- computePosition — Floating UI
- autoUpdate — Floating UI