HTML5: Desactivar el ajuste de línea en un textarea con wrap

En inglés el término wordwrap significa salto de línea automático o simplemente ajuste de línea. El HTML tiene la característica de ajustar el ancho de las líneas al ancho disponible del contenedor, incluso cuando el texto está dentro de un elemento <textarea>. Pero a veces necesitamos que no se produzca este salto automático, como cuando presentamos código de algún programa donde no deben producirse saltos de línea no deseados. En este artículo se expone que posibilidades tenemos para esto y se analiza el nuevo atributo wrap de HTML-5 para el elemento <textarea>.

Los ejemplos de este documento se probaron con las versiones actuales en este momento (11/02/2011) de los navegadores Interner Explorer 8.0.6001, Chrome 9.0.597.94, Safari 5.0.3, Firefox 3.6.13 y Opera 11.01. Por lo tanto es posible que con otras versiones los ejemplos no se ejecuten tal como se expone aquí.

Manejando espacios en blanco con HTML y CSS

La propiedad de estilo CSS2.1 {white-space} se expone en la sección del Glosario CSS. Establece como debe el navegador manejar los espacios en blanco. Los valores posibles son:

  • normal (VALOR INICIAL), se ignoran convirtiéndos en un único espacio cualquier secuencia de espacios, retornos de carro y tabuladores.
  • pre, opuesto al anterior, considerándose todos los espacios tal y como fueron confeccionados en el código.
  • nowrap, comportándose como normal, pero si el ancho de la ventana es menor que el del texto entonces NO produce un salto de línea
  • pre-wrap, considera todos los espacios en contenido generado, incluso los retornos de carro "\A" incluidos en las las cadenas de la propiedad {content}
  • pre-line, ignorándose espacios en contenido generado.
  • inherit (SE HEREDA)

A continuación veremos algunos ejemplos donde aplicamos estilo en línea, es decir, declarado en el propio elemento, por lo que puede consultar el código fuente de esta página para ver los detalles.

En este ejemplo disponemos un elemento de bloque <div> con borde verde y además con un relleno derecho ({padding}) de 5em. En su interior hay otro <div> con borde azul. Tiene un texto con 2 líneas. La primera es muy larga y contiene una tabulación (caracter ASCII 9) después de la primera palabra "Una". La segunda línea (tras el salto de línea ASCII 13+10, denominado a veces "CRLF") es más corta. El bloque interior no tiene ninguna especificación para {white-space}, por lo que toma el valor inicial normal. Todos los espacios se convierten en un único espacio, incluso las tabulaciones y saltos de línea.

Ejemplo:

Una línea de más de veinte caracteres sin ningún salto de línea en medio, con una cantidad larga de caracteres en la misma línea, que incluso con un monitor de 19 pulgadas y un resolución de 1280x1024 píxeles para una fuente Courier New 12pt se sale de la pantalla. Esta segunda línea se inició con un salto de línea.

Este segundo ejemplo es igual, pero al bloque interior le agregamos el estilo white-space: pre. Entonces no modifica los espacios originales, observándose la tabulación tras la primera palabra "Una" y que la primera línea muy larga se sale incluso del contenedor, aunque evitamos que se salga de la pantalla pues a ese contenedor de borde verde le hemos puesto overflow: hidden, pues en otro caso incrementaría el ancho de la ventana excesivamente ajustándolo al ancho del texto.

Ejemplo:

Una línea de más de veinte caracteres sin ningún salto de línea en medio, con una cantidad larga de caracteres en la misma línea, que incluso con un monitor de 19 pulgadas y un resolución de 1280x1024 píxeles para una fuente Courier New 12pt se sale de la pantalla. Esta segunda línea se inició con un salto de línea.

El estilo white-space: pre en un elemento <div> se comporta como el elemento <pre>, aunque en éste no hay que configurar la fuente pues por defecto toma la monoespaciada predefinida del navegador. Para evitar que el texto se salga podemos usar el estilo overflow: auto en el contenedor interior para que recorte lo que sobresale y muestre la barra de desplazamiento cuando esto suceda:

Ejemplo:

Una línea de más de veinte caracteres sin ningún salto de línea en medio, con una cantidad larga de caracteres en la misma línea, que incluso con un monitor de 19 pulgadas y un resolución de 1280x1024 píxeles para una fuente Courier New 12pt se sale de la pantalla. Esta segunda línea se inició con un salto de línea.

El atributo wrap de HTML-5

La solución anterior es idónea para mostrar texto de código de programas, puesto que no queremos que se produzcan saltos de línea no deseados y, al mismo tiempo que se represente el texto tal y como fue configurado inicialmente con todos sus espacios originales, tabulaciones, etc. Pero la cuestión es que pasaría si necesitamos incluir ese texto en un elemento <textarea>. Cambiamos el <div> interior por un <textarea rows="5" cols="20" style="width: 100%;">, forzándolo a que ocupe el ancho disponible del contenedor con borde verde:

Ejemplo:

En XHTML-1.0 (HTML-4.01) los atributos rows y cols son requeridos para el elemento <textarea>. En HTML-5 no son requeridos, tomando los valores 5 filas y 20 columnas por defecto. Pero además los navegadores señalados más arriba permiten un estilo del 100% del ancho ignorando el atributo de columnas, lo que ajusta el elemento a todo el ancho disponible. Aunque no haría falta poner las columnas para que el navegador muestre correctamente el elemento, especificando cols="20" style="width: 100%;" cumplimos el requerimiento de que exista ese atributo.

Vemos que respeta la tabulación inicial y el salto de línea, pero ajusta las líneas al ancho del elemento. Si este texto fuese código de programas no podríamos leerlo adecuadamente si tuviese líneas muy largas. Podíamos incluirle el estilo white-space: pre para ver que sucede:

Ejemplo:

Vemos que no se modifica. Es más, ningún otro valor de los enumerados antes para white-space modifica el comportamiento del ajuste de línea. Esta característica de ajustar automáticamente el ancho de línea o salto automático es lo que se denomina en inglés wordwrap, algo que HTML-4.01 no contempla para el <textarea>, ni tampoco en HTML-5. Estos enlaces a W3C contienen la información relacionada sobre la propiedad {white-space} y el elemento <textarea>:

En CSS3 se observan además dos nuevas propiedades para controlar los saltos automáticos de línea, entre otras:

Sin embargo estas nuevas propiedades así como la modificación de la antigua {white-space} CSS-2.1 que ahora viene a ser una propiedad resumen de las nuevas {white-space-collapsing} y {text-wrap} está aún bajo revisión y quizás es mejor esperar hasta que salga la especificación definitiva. Tenga en cuenta que las especificaciones CSS Text Level 3 (W3C Working Draft 5 October 2010) y HTML5 (W3C Working Draft 13 January 2011) aún son borradores de trabajo, por lo que pueden variar. Por ahora sólo vamos a interesarnos por el <textarea> y su nuevo atributo wrap que en HTML-5 puede tomar los siguientes valores:

  • soft, valor inicial, establece que NO se aplicarán saltos automáticos de línea cuando se remita el formulario (aunque podrán aplicarse los saltos cuando se presente el elemento en pantalla).
  • hard, se incluirán los saltos de línea automáticos cuando se remita el formulario.

En definitiva, este atributo establece si se envían o no los saltos automáticos de línea cuando se remita el elemento con el formulario al servidor. Pero nosotros lo que estamos buscando no es esto precisamente, sino poder actuar sobre esos saltos automáticos en el navegador del usuario. Deseamos desactivar los saltos automáticos de línea en la presentación en el navegador del usuario.

Si consultamos la documentación de Microsoft MSDN <textarea> veremos que contiene el atributo MSDN wrap con los valores soft, hard y además off, con lo que se omiten los saltos automáticos (el wordwrapping) y el texto aparece tal como fue tecleado. Y esto es lo que estamos buscando. Este valor off no parece que se incluya en el nuevo HTML-5 (al menos yo no lo he encontrado). Hagamos unos ejemplos para ver como lo manejan los diferentes navegadores.

En el siguiente ejemplo tenemos dos <textarea> y algunos controles para modificar el atributo wrap del segundo <textarea>. Este es el código HTML resumido:

<textarea rows="5" style="width: 100%">.........</textarea><br />
...
<fieldset><legend>wrap</legend>
    off <input type="radio" value="off" name="radio-wrap" 
        id="radio-wrap" onclick="ponerWrap(this)" checked="checked" />
    soft <input type="radio" value="soft" name="radio-wrap" 
        onclick="ponerWrap(this)" />
    hard <input type="radio" value="hard" name="radio-wrap" 
        onclick="ponerWrap(this)" />               
</fieldset>
...
<textarea rows="5"  
style="width: 100%" id="con-wrap">........</textarea><br />
    

En principio no especificamos el atributo wrap para ninguno de los dos. La razón de esto es que estamos usando un documento especificado en XHTML-1.0 Transitional, es decir, en HTML-4.01 y por tanto este atributo no podrá ser validado. Siempre que sea posible es mejor usar el estándar adecuado y, cuando no queda más remedio, insertar atributos y/o propiedades de estilo mediante JavaScript. Por lo tanto usando JavaScript hacemos lo siguiente para el segundo elemento:

var taConWrap = null;
window.onload = function(){
    taConWrap = document.getElementById("con-wrap");
    ponerWrap(document.getElementById("radio-wrap"));
}
function ponerWrap(este){
    taConWrap.setAttribute("wrap", este.value);
    var tieneAtributoWrap = taConWrap.hasAttribute("wrap");
    if (tieneAtributoWrap) {
        document.getElementById("tiene-atrib").innerHTML = "SI";
        var valor = taConWrap.getAttribute("wrap");
        if (valor == null) valor = "NULO";
        document.getElementById("valor-atrib").innerHTML = valor;
    } else {
        document.getElementById("tiene-atrib").innerHTML = "NO";
        document.getElementById("valor-atrib").innerHTML = "";
    }
} 
    

Con la carga de la página incorporamos el atributo wrap al segundo elemento. Luego con el selector de botones de radio cambiamos el valor de ese atributo para ver el comportamiento:

Ejemplo:

Este no tiene ningún atributo wrap:

Seleccionamos wrap para el siguiente elemento:
wrap off soft hard

¿hasAttribute("wrap")?:
getAttribute("wrap") =

Cuando ponemos el valor off se desactiva el salto automático de línea. Con los otros dos valores soft y hard activa los saltos automáticos. La diferencia estaría en el envío del formulario pues en pantalla se comporta igual como es de esperar, pero el detalle de lo recibido en el servidor no lo vamos a probar (ni siquiera estos elementos están incluidos dentro de un formulario). El comportamiento de los navegadores (las versiones actuales de Febrero 2011) con la aplicación del atributo es el siguiente:

  • Interner Explorer, Chrome y Safari funciona con off valorando su atributo con elemento.setAttribute("wrap", valor) y al mismo tiempo modifican la presentación en pantalla para el caso de off.
  • Firefox y Opera también toman el valor del atributo tal como se observa en la pregunta de si tiene atributo, pero no modifica la presentación cuando cambiamos al valor off.

Si vamos a la documentación de Mozilla (Firefox) para el elemento MDC <textarea> encontramos el atributo wrap pero con los valores especificados en HTML-5 soft y hard. Pero sin embargo ejecutan el valor off. En lugar de usar JavaScript, podemos incluir directamente el atributo en el HTML en estos dos siguientes elementos de ejemplo:

...
<textarea rows="5" style="width: 99%" 
wrap="off">........</textarea><br /> 
...
<textarea rows="5" style="width: 99%" 
wrap="soft">........</textarea><br />   
    

Pero como este documento tiene la extensión php, haremos un pequeño cambio con la misma finalidad (luego explicaremos la razón). Incluiremos el atributo insertándolo con una orden echo:

...
<textarea rows="5" style="width: 99%" 
<?php echo 'wrap="off"' ?>>........</textarea><br /> 
...
<textarea rows="5" style="width: 99%" 
<?php echo 'wrap="soft"' ?>>........</textarea><br />   
    

El resultado en Firefox, Opera (y los otros navegadores señalados) es que ahora sí modifican la presentación en el caso de off, no incluyendo los saltos automáticos de línea tal como deseamos:

Ejemplo:

Con wrap=off puesto inicialmente en el HTML:

Con wrap=soft puesto inicialmente en el HTML:

El motivo de que este documento sea index.php en lugar de index.html es precisamente poder incluir atributos (o elementos) que no formen parte de la especificación declarada en el DOCTYPE, en este caso de XHTML-1.0 (HTML-4.01). Por supuesto que este documento final no podrá ser validado pues tendrá esos atributos wrap, pero cuando trabajamos con un entorno de programación que detecta lo que no es estándar mientras escribimos la página, conviene que no tenga estos errores. En mi entorno trabajo con la pestaña del exlorador del proyecto abierta, de tal forma que incorpora de forma automática una marca de error en cada carpeta o archivo que lo contenga. Si no permitimos estos errores en ningún caso, con un sólo vistazo a la pestaña de exploración podemos ver que todos los documentos están libres de errores.

Inhabilitar el salto automático del textarea con HTML-4.01 y CSS-2.1

Si de todas formas no queremos usar el atributo wrap, la única manera que se me ocurre para conseguir ignorar los saltos automáticos de líneas pasa por una solución un poco forzada, pero que podría valer. Vea el siguiente <textarea> en ejecución donde incluimos 8 líneas numeradas, unas más largas y otras más cortas:

Ejemplo:

Realmente es una composición de un <textarea> dentro de un elemento <div>. El código es el siguiente:

<div  style="height: 7em; overflow: auto; border: gray solid 1px; "
><textarea rows="10" cols="300" style="border: none; overflow: hidden; ">1)...
2)...
3)... 
</textarea></div>

Para el <div> exterior declaramos su altura height: 7em, ponemos automático el desbordamiento (overflow: auto) y le aplicamos un borde (de color gris, no confundir con el otro contenedor exterior que tiene borde verde y que uso para exponer los ejemplos en ejecución). El <textarea> está completamente incrustado en su interior, es decir, no hay espacios ni saltos de línea antes ni después. Con esto logramos que el borde gris del contenedor se ajuste totalmente al <textarea>. El estilo se compone de anular el borde (border: none) y ocultar el desbordamiento (overflow: hidden), pues ambos serán ejecutados en el contenedor exterior.

Lo más importante es que el número de columnas (cols="300") tiene que ser mayor que el número de caracteres de la línea más larga, pues en otro caso produciría un salto automático en esa línea. Elegir 300 caracteres es porque para una fuente como la monoespaciada de 12 puntos, la que hemos declarado para los <textarea> de esta página, una línea de 300 caracteres es mucho más larga que el ancho de un monitor con una resolución de 1280x1024 píxeles.

La resolución 1280x1024 es la máxima que yo tengo en mi monitor de 19". Sobre todo lo que interesa es la resolución horizontal para nuestro caso (1280 píxeles). Según se observan en los datos que expone el sito www.desarrolloweb.com sobre ranking resoluciones de pantalla de Enero de 2011, las de 1280 en horizontal están entre las más usadas. (En esa página pone 280x1024 pero debe querer decir 1280x1024). De todas formas habría que declarar tantas columnas como las necesarias para que no se ajuste la línea con la mayor resolución horizontal posible, 1920 según la estadística anterior.

En cuanto al número de filas (rows="10") tiene que ser igual o mayor que el número de líneas que tiene el <textarea>, pues en otro caso el control de la barra de desplazamiento vertical (que es la del <div>) no funcionará adecuadamente. La altura de este <div> puede ser cualquiera, incluso auto que es su valor por defecto si no se especifica.

Modificar filas y columnas de un textarea con JavaScript

En el ejemplo anterior el problema surge cuando el usuario incrementa el número de líneas de tal forma que sobrepasa las 10 declaradas en rows, o bien hace una línea más larga que los 300 caracteres declarados en cols, puesto que entonces no funcionan adecuadamente las barras de desplazamiento del contenedor exterior. En Opera además cuando añadimos nuevas líneas se produce un incremento de la altura del elemento ocasionando un verdadero desastre. La solución podría basarse en controlar mediante JavaScript los cambios del texto que haga el usuario. Vea el siguiente ejemplo:

Ejemplo:

Ahora podemos agregar nuevas líneas e incrementar el número de caracteres por línea, de tal forma que con cada cambio del texto se modifican los atributos cols y rows. El código HTML es el siguiente:

<div style="height: 7em; overflow: auto; border: gray solid 1px; "
><textarea rows="10" cols="300" 
style="border: none; overflow: hidden;"
id="ta" 
onkeypress="ajustaTextarea(this)">
...</textarea></div>    
    

El JavaScript que controla esto es el siguiente:

function ajustaTextarea(este){
    var texto = este.value;
    var cols = 300;
    var rows = 10;
    if (texto != "") {
        var arr = texto.split("\n");
        var maxCols = 0;
        for (var i in arr){
            var longitud = arr[i].length;
            if (longitud > maxCols) maxCols = longitud;
        }
        if (maxCols > 0) cols = parseInt(maxCols) + 100;
        if (arr.length > 0) rows = parseInt(arr.length) + 1;
    }
    este.setAttribute("rows", rows);
    este.setAttribute("cols", cols);
}

Se trata de buscar la línea más larga y contar sus caracteres para actualizar el atributo cols. Le sumamos un espacio adicional de 100 para facilitar que el usuario pueda escribir más caracteres en esa línea. El número de filas se actualiza también pues lo que hacemos es pasar a un array el texto dividiéndolo por los saltos de línea \n. No usamos \r\n porque Firefox los transforma en sólo \n y después de todo así sirve para ambos casos. También le agregamos una fila adicional para que cubra el espacio que ocuparía, en su caso, la barra de desplazamiento horizontal.

El evento onkeypress se produce con cada caracter que se teclea, aunque también funciona con onchange en Explorer pero no va bien con Firefox. Otra situación es que si copiamos texto y lo pegamos, o bien cortamos texto, no se produce la actualización con el evento onkeypress, y aunque Explorer si lo detecta con onchange, nuevamente Firefox no lo detecta. Para completar el efecto sería necesario incluir los eventos onpaste y oncut, pero con Firefox estos eventos ocurren antes de que el texto sea pegado o cortado y, por tanto, no modificarán las filas y columnas pues el elemento aún no contiene el nuevo texto. Con el evento onblur se detecta cuando salgamos del elemento y ubiquemos el cursor en algún otro elemento que pueda recoger el foco, por lo que si salimos del <textarea> con el ratón y movemos la barra de desplazamiento vertical entonces no se activa el evento y no se modifican las filas y columnas (de hecho con esta acción el foco sigue estando en el <textarea>).

Y es que cuando nos metemos con el "submundo" de los eventos, las diferencias entre navegadores se acrecienta, por lo que prefiero dejar el ejemplo con el evento onkeypress tal como está.