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:
- El contenido textual se renderzia en una única línea, y se trunca con una elipsis si supera el ancho disponible.
- 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.
- 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;ytext-overflow: ellipsis;para el truncamiento. Las pseudoclases:hovery la propiedadtransitionse 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');