javascript - property - ¿Por qué Text.splitText() afecta el diseño?
modify css property javascript (2)
Digamos que tenemos un párrafo en nuestra página, con un solo bloque de texto.
<p>laborum beatae est nihil, non hic ab, deserunt repellat quas. Est molestiae ipsum minus nesciunt tempore voluptate laboriosam</p>
DOM-sabio, la estructura es:
HTMLParagraphElement
Text [laborum beatae est nihil...]
Ahora lo dividimos (con Text.splitText()
) dos veces, para separar el fragmento "deserunt repellat quas. Est" . La estructura se convierte en:
HTMLParagraphElement
Text [laborum beatae est nihil...]
Text [deserunt repellat quas. Est]
Text [ molestiae ipsum minus nesciunt...]
Si bien esta operación afecta a DOM, nunca la cambia en el nivel Elemento (¡Texto! == Elemento), por lo que no esperaba cambios visuales.
Sin embargo, splitText()
afecta al diseño, activando tanto la retransmisión como el repintado en todos los navegadores probados (Chrome 60, Firefox 55, Edge 14, todo en el sistema operativo Windows 10). Lo mismo sucede cuando llamamos a ParagraphElement.normalize()
, reduciendo el número de nodos de texto a 1; de nuevo se activan tanto el relé como el repintado.
Hay un efecto secundario desagradable de esto, que se puede ver en esta demostración . Si marca las palabras cerca de ''quas. ¡Est '', ves que realmente cambian de posición!
Es claramente visible en Firefox, y es mucho más sutil (pero también distinguible) en Chrome. Para mi diversión, no hubo tal "baile de palabra" en Edge.
La razón por la que esto es importante se muestra en esta demostración de (tipo de) motor de selección shimmed. Esta versión en particular no funcionará en Firefox (aún no es compatible con caretRangeFromPoint
- ¡argh!), Pero incluso con "point2dom" cableado en caretPositionFromPoint
el texto resaltado se reposiciona allí, tanto en Chrome, o incluso peor. Una vez más, parece funcionar bien en Edge.
Así que, de hecho, estoy más interesado en comprender las razones y encontrar soluciones alternativas.
Aquí está el gif animado que muestra cómo se reproduce la primera demostración en Chrome (solo disparo un clic en el intervalo)
El temblor ES sutil aquí, pero aún se puede observar en todas las palabras. Estoy especialmente desconcertado por la razón por la que i
estremecen las molestiae
, ya que las letras circundantes parecen quedarse donde están.
Y empeora (mucho peor) con fuentes menos comunes y más texto, como en la demostración de selección.
Cambiando a font-family:monospace
no resolvió esto, pero lo hizo aparentemente peor:
Cambiar de font-kerning
a none
tampoco ayudó.
ACTUALIZACIÓN: El problema está registrado en el seguidor de Blink.
tl: versión dr
splitText()
puede ser la acción que está provocando el cambio, pero el cambio se debe en realidad a que el dominio actualizado se ejecuta a través del motor de justificación de texto. Cambiar desde text-align: justify;
text-align: left;
para ver lo que quiero decir.
Apéndice
Eso maneja las palabras que se mueven alrededor. En cuanto a las letras que se desplazan cuando se desactiva la justificación, esto es un poco más difícil de cuantificar, pero parece ser un umbral de redondeo que se está cruzando.
Respuesta completa
Consideraciones en la justificación del texto
La justificación del texto es complicada de implementar, por decir lo menos. Para simplificar, hay tres consideraciones básicas:
- Exactitud
- Estética
- Velocidad
Una ganancia en cualquiera, requiere una pérdida en uno o en ambos. Por ejemplo, InDesign, que favorece la estética, utiliza el espaciado entre palabras (positivo y negativo), el espaciado entre letras (positivo y negativo) y la consideración de todas las líneas de un párrafo para encontrar el diseño más agradable a un costo de velocidad y permite la óptica. Alineación de márgenes a costa de la precisión. Pero como solo pasa por todo esto una vez y almacena los resultados en el archivo, puede ser muy lento (relativamente).
Los navegadores tienden a preocuparse mucho más por la velocidad, en parte porque deben poder justificar el texto rápidamente en hardware obsoleto, pero también porque gracias a la interactividad que ahora disfrutamos, a veces es necesario volver a justificar el mismo bloque de texto miles de veces durante una sesión.
Variación en la implementación del navegador
La spec es un tanto vaga sobre el tema de la justificación, diciendo cosas como:
Al justificar el texto, el agente de usuario toma el espacio restante entre los extremos de los contenidos de una línea y los bordes de su cuadro de línea, y distribuye ese espacio en todo su contenido para que el contenido llene exactamente el cuadro de línea. El agente de usuario puede distribuir alternativamente un espacio negativo, poniendo más contenido en la línea del que cabría en condiciones de espaciado normales.
Y
Para la justificación automática, esta especificación no define cuáles son todas las oportunidades de justificación, cómo se priorizan, o cuándo y cómo interactúan los múltiples niveles de oportunidades de justificación.
Como resultado, cada navegador es libre de optimizar esta funcionalidad como mejor le parezca.
Un ejemplo
Un motor de justificación de texto moderno es más complicado de lo que puede explicarse razonablemente en el espacio que tenemos aquí. Agregue que están ajustados continuamente para encontrar un mejor equilibrio entre las consideraciones principales y todo lo que escribo aquí estará desactualizado en unos pocos nanosegundos de todos modos. Voy a usar un algoritmo de justificación de texto muy antiguo (y mucho más sencillo) para demostrar cómo un motor puede tener dificultades para rendir consistentemente en esta situación.
Supongamos que tenemos la cadena ''Lorem ipsum dolor sit amet, consectetur.''
y podemos caber 35 caracteres en una línea. Así que vamos a utilizar este algoritmo de justificación:
- Crear una matriz
- Trabajar a través de la cadena hasta que encuentre el final de la palabra o el signo de puntuación seguido de un espacio o final de la cadena
- Cuando encuentre el final de una palabra, verifique si la longitud de la palabra + la longitud de todas las palabras en la matriz más el número de oportunidades de justificación. Si es así, recórtelo de la cadena y colóquelo en la matriz.
- Cuando ya no se puedan agregar más palabras a la matriz, tome la diferencia entre el espacio necesario y el espacio disponible, divida por el número de oportunidades de justificación y dibuje la matriz, colocando esa cantidad de espacio entre cada palabra.
Utilizando este algoritmo:
Dividir la cadena
''Lorem ipsum dolor sit amet, consectetur.'' => [''Lorem'',''ipsum'',''dolor'',''sit'',''amet,''] & ''consectetur.''
Con 23 caracteres de ancho de texto y 35 caracteres de espacio disponible, agregamos 3 espacios entre cada palabra (estoy cuadruplicando el espacio para enfatizar, lo que será importante más adelante)
------------------------------------------------------------------------- |Lorem ipsum dolor sit amet,| |consectetur. | -------------------------------------------------------------------------
Esto es rápido porque podemos dibujar todo de izquierda a derecha sin necesidad de retroceder, y no necesitamos mirar hacia adelante.
Si ejecutamos textSplit
en esto y lo convertimos efectivamente en una matriz:
[''Lorem ipsum dolor '',''sit'','' amet, consectetur.'']
Así que necesitaríamos modificar nuestras reglas, cambiemos la regla 2 para trabajar a través de cada cadena en la matriz, siguiendo las mismas reglas que antes.
Divida las cadenas, tenga en cuenta que hay un espacio antes de amet, por lo que la palabra límite no lo atrapará
`[''Lorem ipsum dolor '',''sit'','' amet, consectetur.'']` => [''Lorem'',''ipsum'',''dolor'',''sit'','' amet,''] & ''consectetur.''
Con 24 caracteres de ancho de texto y 35 caracteres de espacio disponibles, agregamos 2.75 espacios entre cada palabra (nuevamente, cuadruplicando el espacio). El espacio adicional en la cadena de amet también se dibuja.
------------------------------------------------------------------------- |Lorem ipsum dolor sit amet,| |consectetur. | -------------------------------------------------------------------------
Si miramos las dos líneas de lado a lado, podemos ver la diferencia.
-------------------------------------------------------------------------
a |Lorem ipsum dolor sit amet,|
b |Lorem ipsum dolor sit amet,|
-------------------------------------------------------------------------
Una vez más, estos son exagerados, un cuarto de espacio en la vida real, esto solo sería un píxel o dos.
Nuestro conjunto de reglas es muy simple, por lo que podríamos resolver este problema muy fácilmente.
Considere qué tan complicada sería la depuración cuando tiene un motor de justificación que tiene que admitir:
- Otras propiedades css (ajustables) para justificación
- Múltiples idiomas y todas sus reglas.
- Fuentes que tienen:
- Anchos de caracteres variables
- Métricas de kerning
- Vectores que no necesariamente se alinean con un píxel
- Elementos en línea (y en línea) que tienen su propio estilo
- ...incesantemente
Sin mencionar, la mayor parte de esto se transfiere a la GPU para dibujar.
De todos modos todo esto para decir.
Tenga en cuenta que, de hecho, está alterando el dominio y obligando a que todo el bloque vuelva a renderizarse como resultado. Dado el número de factores involucrados, es muy difícil esperar que dos estructuras de domo diferentes se vuelvan exactamente iguales.
Apéndice
Con respecto a las letras que parecen cambiar un poco de vez en cuando, la mayor parte de lo que he dicho acerca de la complejidad de cómo se manejan estas cosas continúa aplicándose a lo siguiente.
Nuevamente, en un esfuerzo por mejorar la velocidad, los números a menudo se redondean hacia abajo antes de pasarlos a la GPU para su procesamiento.
A modo de proporcionar un ejemplo simplificado, una centésima parte de un píxel no hace mucha diferencia, sería imperceptible para el ojo humano y, por lo tanto, es un desperdicio de poder de procesamiento. Así que decides redondear al pixel más cercano.
Digamos que la secuencia de dibujo del personaje es:
- Toma el inicio del contenedor.
- Agregar el desplazamiento
- Dibuja el personaje en esta ubicación, redondeado al píxel más cercano
- Actualice el desplazamiento agregando el ancho no redondeado del carácter.
Caracteres con anchos:
10.2 10.3 10.4 10.2 10.6 11 8.9 9.9 7.6 9.2 9.8 10.4 10.4 11.1 10.5 10.5
Comenzando en el punto 0 dibujaremos en:
0 10 21 31 41 52 63 72 82 89 98 108 119 129 140 151
Pero, ¿y si llegas al final de un nodo? Bueno, eso es fácil. ¿Simplemente inicie el nuevo nodo en la siguiente posición de sorteo y continúe?
Caracteres con anchos:
10.2 10.3 10.4 10.2 10.6 11 8.9|9.9 7.6 9.2 9.8 10.4 10.4 11.1 10.5 10.5
Comenzando en el punto 0 dibujaremos en:
0 10 21 31 41 52 63 72 82 89 99 108 119 129 140 151
A pesar de que los números subyacentes son diferentes, la ubicación de procesamiento sigue siendo la misma para cada ubicación, excepto el carácter 11, debido al redondeo.
Puede que no sea necesariamente la posición de inicio, de nuevo, aquí hay una tremenda complejidad. Los síntomas apuntan a un umbral de redondeo de algún tipo. Como dije antes, cuando se renderizan dos árboles dom diferentes, se deben esperar diferencias.
Es de esperar que la retransmisión / repintado, ya que los nodos de texto también son nodos DOM ... No son elementos DOM, pero los navegadores tienen que reconsiderar el diseño, incluso si espera que permanezca igual, es posible que tengan que movimiento. Tal vez por el kerning.
Ahora, ¿por qué dividir el texto causa un movimiento? Lo que esperaría es porque los navegadores dibujan las partes de texto por separado. Dos letras vecinas usualmente tienen un espacio que puede ser reducido por la fuente dependiendo de las letras, tome "WA", por ejemplo, el final de la W está sobre el inicio de la A, que se llama kerning (Thx Ismael Miguel). Cuando los nodos de texto se dibujan por separado, cada uno tiene que terminar antes de que comience el próximo, por lo que puede crear un espacio más grande entre esas letras ya que evita el kerning.
Lo siento, el espacio entre las letras tiene un nombre pero lo olvidé ...
.one {
background-color: #FF9999;
}
.two {
background-color: #99FF99;
}
body {
font-size: 40px;
}
div>span {
border: 1px solid black;
}
<div><span>AW</span> - in the same DOM node.</div>
<div><span><span>A</span><span>W</span></span> - in two different nodes</div>
<div><span><span class="one">A</span><span class="two">W</span></span> - in two different nodes, colored</div>
En cuanto a cómo prevenir este comportamiento, la solución más fácil es usar una fuente monoespaciada. Esto podría no ser siempre estéticamente factible. El kerning es una información incrustada en los archivos de fuentes, y eliminar una fuente de esta información parece ser la forma más robusta de evitar el parpadeo. Además, la propiedad CSS font-kerning podría ayudar cuando se establece en none
.
Otra forma es agregar elementos absolutos detrás o delante del texto para imitar el hecho de rodear una parte del texto en un elemento, pero eso depende de la meta final.
Mirando un poco más CSS-Tricks tiene un buen artículo sobre el renderizado de texto y también podría ayudar en el kerning de fuentes.
EDITAR: Al escribir esta respuesta, había pasado por alto el hecho de que el texto estaba justificado. Si bien mi respuesta explica por qué podría ocurrir un cierto parpadeo al cortar un nodo de texto en varios, no explica de ninguna manera por qué los navegadores parecen tener problemas para calcular los espacios justificados.