Separar un texto en divs dinámicamente

Revisando mi código viejo, hice hace tiempo un editor de texto en HTML, pensando en que fuera un Editor en línea tipo Word HTML5, con puro CSS, JavaScript y jQuery, pues en ese entonces no había Office 365. Mi problema es que aunque se pinta una página, esta página se agranda infinitamente. Eso se debe a que me faltó separar un texto en divs dinámicamente, el texto del usuario al ser editable puede ser cualquier cantidad de texto, a veces son 200 líneas de texto, otras veces apenas 2 elementos, lo cual nos presenta un problema un poco fuera de lo común, pues cada página es un div, que debe de tener una altura igual, y una separación constante entre cada hoja.

La solución debe:

  • Mantener todo dentro del <div> contenedor.
  • Funcionar con contenido dinámico.
  • Evitar cambiar la estructura a <table> u otros elementos.

Paso 1: Bloques de tamaño fijo con CSS

Primero, definimos columnas de cierta altura, por ejemplo 200 píxeles. Para mantener el contenido dentro de esos límites sin que se vea el excedente, usamos overflow: hidden y un borde inferior como separador.

.column {
  height: 200px;
  overflow: hidden;
  border-bottom: 1px dotted gray; /* Línea separadora */
  margin-bottom: 10px;
  padding: 10px;
}

Paso 2: Distribuir el contenido dinámicamente con JavaScript

El siguiente reto es evitar que el contenido se corte de manera brusca. Para eso, usamos una función que reparte el texto en cada bloque y mueve el sobrante al siguiente.

Estructura HTML

El HTML inicial es sencillo:

<div id="container">
  <div class="column"></div>
</div>

Simplemente tenemos nuestro contenedor que es el área gris, y cada column es una página.

Aunque tengamos una sola página que es un div, con JavaScript agregaremos más páginas y nos encargaremos de separar un texto en divs, ¿Qué texto? El texto que el usuario escriba.

La función distributeText

function distributeText(fullText, containers) {
  let remainingText = fullText;
  ...
}

👉 Esta función recibe:

  • fullText: es todo el texto que queremos mostrar.
  • containers: un arreglo de elementos HTML (nuestros divs, que son filas).

La idea es llenar cada div con la mayor cantidad de texto posible, y si sobra texto, pasarlo al siguiente bloque, para eso guardamos el texto que no quepa en remainingText.

Recorremos cada bloque

con el ciclo de nuestra preferencia iteramos cada elemento que queremos que contenga texto, es decir, cada página que tenga, y al inicio solamente va a tener una.

for (let i = 0; i < containers.length; i++) {
  const container = containers[i];
  container.innerHTML = remainingText;

Aquí tomamos cada bloque .column y le metemos el texto que queda (remainingText).

Detectamos si el texto se desborda

Para obtener las medidas de cada div usaremos dos propiedades de solo lectura que pertenecen a la API HTMLElement, en concreto scrollHeight y clientHeight.

if (container.scrollHeight > container.clientHeight) {
  • scrollHeight: cuánto mide el contenido real.
  • clientHeight: cuánto espacio tiene disponible el contenedor (200px).

👉 Si el contenido mide más que el contenedor, significa que se desbordó.

Ajustamos hasta que encaje

Para saber que texto es visible en un div usamos la propiedad innerText del api HTMLElement.

let textContent = container.innerText;
let lastValidIndex = textContent.length;

while (container.scrollHeight > container.clientHeight && lastValidIndex > 0) {
  lastValidIndex--;
  container.innerText = textContent.substring(0, lastValidIndex);
}

Aquí lo que hacemos es recortar el texto poco a poco hasta que encaje dentro de los 200px.

  • substring(0, lastValidIndex) → va eliminando las últimas letras una por una.
  • Cuando finalmente el texto cabe, paramos.

Guardamos lo que sobró

remainingText = textContent.substring(lastValidIndex);

Todo el texto que ya no cabía se guarda en remainingText. Ese será el texto que pasaremos al siguiente bloque .column.

Salimos si ya no queda texto

if (remainingText === "") break;

Esto evita revisar los bloques restantes si ya acabamos.

Creamos más bloques si hace falta

Aquí para resolverlo había dos opciones, con recursividad o de forma iterativa. como estamos modificando el dom directamente sinceramente el desempeño sería pésimo con ambas opciones, así que fui por la más entendible, con un while.

let pendientes = distributeText(fullContent, containers);
while (pendientes > 0) {
  const container = document.getElementById('container');
  const div = document.createElement("div");
  div.setAttribute("class", "column");
  container.append(div);

  pendientes = distributeText(fullContent, Array.from(document.querySelectorAll('.column')));
}
  • pendientes guarda el número de caracteres que aún no se han colocado en ningún bloque.
  • Mientras quede texto pendiente, creamos un nuevo bloque .column y volvemos a ejecutar la función haciendo todo desde el inicio.
  • Así, el script seguirá generando columnas automáticamente hasta mostrar todo el contenido.

  • Cursos de Código Facilito

    Te comparto aquí mis recomendaciones de cursos de Código Facilito. Codigofacilito.com es una plataforma virtual de cursos en idioma exclusivamente español que inició como un canal de YouTube. Opinión de la plataforma En mi opinión la plataforma aún es incipiente y los cursos necesitan más trabajo, ya que es muy común hallarse con clases de…


  • Cursos de LinkedIn Learning

    Te comparto aquí mis recomendaciones de cursos de Linkedin Learning. LinkedIn Learning es una plataforma virtual de cursos propiedad de Microsoft. Hallarás mucho material en inglés, pues prácticamente todos los cursos vienen con subtítulos en castellano y en la parte técnica son muy buenos debido a la adquisición de Lynda.com. En mi opinión la plataforma…


  • Mockito: Snippets de código

    Estos snippets de código para entrevista acerca de mockito espero te sean de mucha utilidad. Claro que también los puedes usar para tu desarrollo en el día a día. Mock básico Si te llegan a mostrar algo como esto es mejor que sospeches, mockito se usa principalmente para pruebas de integración. Si llegas a ver…


javapro.org