Componente de Texto Truncado con Expansión y Flecha Indicadora Dinámica en Vue.js

Este artículo describe la creación de un componente de interfaz de usuario para presentar texto largo de manera compacta. El objetivo principal es mostrar una sola línea de texto, aplicando puntos suspensivos cuando el contenido excede el espacio asignado. Una característica clave es la inclusión de un indicador visual en forma de flecha, que se hace visible únicamente cuando el texto se ha truncado y que cambia su orientación al pasar el ratón, revelando el texto completo.

Objetivos de la Interacción:

  1. El contenido textual se renderzia en una única línea, y se trunca con una elipsis si supera el ancho disponible.
  2. Se muestra una flecha indicadora a la derecha del texto solo si este ha sido truncado. Si el texto cabe por completo, la flecha permanece oculta.
  3. Al situar el puntero del ratón sobre el componente, el texto se expande para mostrar todo su contenido (permitiendo el ajuste de línea), y la flecha cambia su dirección, indicando la expansión.

Estrategia de Implementación:

La funcionalidad se logra combinando de forma efectiva CSS para los efectos visuales y una directiva personalizada de Vue.js para la lógica de detección de desbordamiento de texto.

  • Estilos CSS: Se emplean propiedades como white-space: nowrap;, overflow: hidden; y text-overflow: ellipsis; para el truncamiento. Las pseudoclases :hover y la propiedad transition se utilizan para animar la expansión y la rotación de la flecha.
  • Directiva Vue.js: Una directiva personalizada detectará cuándo el texto excede su contenedor y controlará la visibilidad inicial de la flecha indicadora.

Desarrollo del Componente:

1. Definición del Componente ExpandableTextWidget

Este componente contendrá el texto y la flecha, gestionando su comportamiento interactivo.

ExpandableTextWidget.vue (Estructura HTML)
<template>
 <div class="text-display-container">
   <!-- Área donde se muestra el texto. La directiva 'v-overflow-check' controlará el indicador -->
   <span v-overflow-check class="text-content-element">{{ itemData.textLabel }}</span>
   <!-- Indicador de flecha, inicialmente invisible por CSS, activado por la directiva -->
   <span class="expand-arrow-indicator"></span>
 </div>
</template>

<script>
export default {
 name: 'ExpandableTextWidget',
 props: {
   itemData: {
     type: Object,
     required: true,
     default: () => ({ textLabel: 'Un ejemplo de texto que es bastante largo y probablemente se truncará en un espacio limitado.' })
   }
 }
};
</script>

ExpandableTextWidget.vue (Estilos SCSS)

Estos estilos configuran la presentación del componente, incluyendo el comportamiento de truncamiento y las transiciones de la flecha.

.text-display-container {
 border-radius: 8px;
 cursor: pointer;
 background-color: #f7faff; // Color de fondo claro y moderno
 height: 36px;
 position: relative; // Fundamental para el posicionamiento absoluto de los hijos
 margin: 12px 16px; // Espaciado exterior
 color: #333;
 display: flex; // Facilita la alineación
 align-items: center;
 overflow: visible; // Permite que el contenido expandido se muestre fuera de los límites iniciales

 .text-content-element {
   position: absolute; // Permite que el texto se superponga al expandirse
   top: 0;
   left: 0;
   width: 100%; // Ocupa el ancho completo del padre inicialmente
   padding: 8px 14px; // Espaciado interno
   box-sizing: border-box;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   line-height: 20px;
   max-width: calc(100% - 30px); // Reserva espacio para la flecha y su margen
   transition: all 0.2s ease-in-out; // Transición suave para todos los cambios
   z-index: 1; // Prioridad z-index baja para el estado normal
 }

 .expand-arrow-indicator {
   position: absolute;
   right: 10px; // Posición de la flecha desde la derecha
   top: 50%;
   transform: translateY(-50%) rotate(0deg);
   transform-origin: center;
   width: 20px; // Ancho fijo para la flecha
   height: 20px;
   font-size: 12px;
   text-align: center;
   line-height: 20px;
   transition: transform 0.1s ease-in-out, opacity 0.1s ease-in-out; // Transición para rotación y opacidad
   color: #666;
   opacity: 0; // Oculto por defecto
   pointer-events: none; // No interactuable directamente

   &::before {
     content: '▼'; // Un carácter de flecha simple
     display: block;
   }
 }

 // Estilos al pasar el ratón
 &:hover {
   .text-content-element {
     white-space: normal !important; // Permite que el texto se ajuste y envuelva
     overflow: visible !important; // Muestra todo el contenido sin ocultar
     background-color: #e0f0ff; // Cambia el fondo para indicar estado activo
     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); // Sombra para un efecto de "elevación"
     z-index: 9; // Mayor prioridad z-index para el texto expandido
     width: auto; // Ancho se ajusta al contenido
     max-width: unset !important; // Elimina la restricción de ancho máximo
     padding: 8px 14px;
   }
   .expand-arrow-indicator {
     z-index: 10; // Flecha por encima del texto expandido
     transform: translateY(-50%) rotate(180deg); // Rota la flecha hacia arriba
     opacity: 1; // Asegura que la flecha sea visible si ya estaba activa
     pointer-events: auto; // Puede volverse interactuable si se desea
   }
 }
}

2. Directiva Personalizada v-overflow-check

Esta directiva evalúa si el texto de un elemento desborda su contenedor y, en consecuencia, cotnrola la visibilidad de la flecha indicadora asociada.

// Archivo: `src/directives/overflowCheck.js`
import Vue from "vue";

Vue.directive("overflow-check", {
 inserted(el) {
   const container = el.parentNode;
   const arrowIndicator = container.querySelector('.expand-arrow-indicator');

   if (!arrowIndicator) {
     console.warn("La directiva 'v-overflow-check' requiere un elemento '.expand-arrow-indicator' como hermano o descendiente del elemento padre.");
     return;
   }

   // Usamos nextTick para asegurar que el DOM esté completamente renderizado
   // y las dimensiones calculadas sean precisas.
   Vue.nextTick(() => {
     // Comparar el ancho natural del contenido (scrollWidth) con el ancho renderizado (offsetWidth)
     // para detectar si el texto está siendo truncado.
     const contentNaturalWidth = el.scrollWidth;
     const contentRenderedWidth = el.offsetWidth;

     if (contentNaturalWidth > contentRenderedWidth) {
       // El texto desborda su espacio asignado, por lo tanto, mostrar la flecha.
       arrowIndicator.style.opacity = '1';
       arrowIndicator.style.pointerEvents = 'auto'; // Habilitar la interacción con la flecha
     } else {
       // El texto cabe dentro del espacio, ocultar la flecha.
       arrowIndicator.style.opacity = '0';
       arrowIndicator.style.pointerEvents = 'none'; // Deshabilitar la interacción con la flecha
     }
   });
 },
 // La lógica se reevalúa cuando el componente que usa la directiva se actualiza,
 // útil para contenido dinámico.
 componentUpdated(el) {
   this.inserted(el); // Re-ejecutar la lógica de inserción
 }
});

Para integrar esta directiva en su aplicaicón Vue.js, regístrela globalmente, por ejemplo, en main.js:

// main.js
import Vue from 'vue';
import App from './App.vue';
import './directives/overflowCheck'; // Importa y registra la directiva personalizada

new Vue({
 render: h => h(App),
}).$mount('#app');

Etiquetas: vue.js css UI/UX Truncamiento de Texto Directivas Personalizadas Vue

Publicado el 9-29 06:57