383 lines
9.8 KiB
Markdown
383 lines
9.8 KiB
Markdown
|
|
# Componentes Personalizados para Slidev
|
|||
|
|
|
|||
|
|
## ProjectionToggle - Toggle para Modo Proyector
|
|||
|
|
|
|||
|
|
### 📍 Ubicación
|
|||
|
|
`slides/components/ProjectionToggle.vue`
|
|||
|
|
|
|||
|
|
### 🎯 Descripción
|
|||
|
|
Botón flotante para cambiar entre modo Monitor (oscuro) y modo Proyector (más claro) durante la presentación.
|
|||
|
|
|
|||
|
|
### ✨ Características
|
|||
|
|
|
|||
|
|
1. **Toggle visual**: Botón flotante con iconos 🖥️ / 📽️
|
|||
|
|
2. **Persistencia**: Guarda la preferencia en localStorage
|
|||
|
|
3. **Feedback visual**: Cambia de color según el modo activo
|
|||
|
|
4. **No invasivo**: Se oculta automáticamente en modo presentador y al imprimir
|
|||
|
|
5. **CSS Variables**: Usa variables CSS para cambios instantáneos
|
|||
|
|
|
|||
|
|
### 📝 Uso
|
|||
|
|
|
|||
|
|
Agrégalo a tu primera slide o a todas las slides principales:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<ProjectionToggle />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
O en markdown:
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
---
|
|||
|
|
# Primera Slide
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
<ProjectionToggle />
|
|||
|
|
|
|||
|
|
# Tu contenido aquí...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🎨 Estados Visuales
|
|||
|
|
|
|||
|
|
**Monitor Mode** (por defecto)
|
|||
|
|
- Icono: 🖥️ Monitor
|
|||
|
|
- Color: Naranja (gradiente Rust)
|
|||
|
|
- Fondo: #1c1c1c (muy oscuro)
|
|||
|
|
|
|||
|
|
**Projection Mode**
|
|||
|
|
- Icono: 📽️ Projection
|
|||
|
|
- Color: Verde (#48bb78)
|
|||
|
|
- Fondo: #2d3748 (dark pero más visible)
|
|||
|
|
|
|||
|
|
### 💡 ¿Por qué dos modos?
|
|||
|
|
|
|||
|
|
Los proyectores tienen menor contraste que los monitores. El modo Projection:
|
|||
|
|
- Fondo más claro (#2d3748 vs #1c1c1c)
|
|||
|
|
- Mayor contraste de texto
|
|||
|
|
- Bordes más gruesos en componentes
|
|||
|
|
- Mejor visibilidad en salas con luz ambiente
|
|||
|
|
|
|||
|
|
### 🔧 Cómo Funciona
|
|||
|
|
|
|||
|
|
1. Click en el botón
|
|||
|
|
2. Agrega/quita la clase `projection-mode` al `<html>`
|
|||
|
|
3. Las CSS variables cambian automáticamente:
|
|||
|
|
```css
|
|||
|
|
:root {
|
|||
|
|
--bg-primary: #1c1c1c; /* Monitor */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.projection-mode {
|
|||
|
|
--bg-primary: #2d3748; /* Projection */
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
4. Guarda la preferencia en `localStorage`
|
|||
|
|
|
|||
|
|
### 📖 Documentación Completa
|
|||
|
|
|
|||
|
|
Ver: `slides/PROJECTION_MODE.md` para guía detallada sobre:
|
|||
|
|
- Todos los métodos de activación
|
|||
|
|
- Comparación visual
|
|||
|
|
- Personalización de colores
|
|||
|
|
- Troubleshooting
|
|||
|
|
|
|||
|
|
### 🎯 Recomendación de Uso
|
|||
|
|
|
|||
|
|
Antes de tu presentación:
|
|||
|
|
1. Agrega `<ProjectionToggle />` a la primera slide
|
|||
|
|
2. Durante el soundcheck con el proyector, prueba ambos modos
|
|||
|
|
3. El modo elegido se guardará automáticamente
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## YearsCounter - Contador Animado de Años
|
|||
|
|
|
|||
|
|
### 📍 Ubicación
|
|||
|
|
`slides/components/YearsCounter.vue`
|
|||
|
|
|
|||
|
|
### 🎯 Descripción
|
|||
|
|
Componente SVG animado que muestra un contador de años pasando como hojas de calendario, desde 1 hasta el año actual.
|
|||
|
|
|
|||
|
|
### ✨ Características
|
|||
|
|
|
|||
|
|
1. **Animación de contador**: Los números pasan de 1 hasta 40 (en 2025)
|
|||
|
|
2. **Efecto de hoja**: Animación de scale que simula una página de calendario volteándose
|
|||
|
|
3. **Badge animado**: Fondo con gradiente verde y efecto de brillo pulsante
|
|||
|
|
4. **SVG puro**: Más ligero y eficiente que componentes complejos
|
|||
|
|
5. **Totalmente personalizable**: Props para ajustar año inicial y velocidad
|
|||
|
|
|
|||
|
|
### 📝 Uso
|
|||
|
|
|
|||
|
|
En cualquier slide de Slidev:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<YearsCounter :startYear="1985" :speed="80" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🎛️ Props
|
|||
|
|
|
|||
|
|
| Prop | Tipo | Default | Descripción |
|
|||
|
|
|------|------|---------|-------------|
|
|||
|
|
| `startYear` | Number | 1985 | Año desde el que se cuenta |
|
|||
|
|
| `speed` | Number | 80 | Velocidad en milisegundos por año (menor = más rápido) |
|
|||
|
|
| `delay` | Number | 3000 | Delay en ms antes de iniciar la animación (después de que la slide sea visible) |
|
|||
|
|
|
|||
|
|
### 🔧 Ejemplos de Configuración
|
|||
|
|
|
|||
|
|
#### Más Rápido (50ms por año)
|
|||
|
|
```vue
|
|||
|
|
<YearsCounter :startYear="1985" :speed="50" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Más Lento (150ms por año)
|
|||
|
|
```vue
|
|||
|
|
<YearsCounter :startYear="1985" :speed="150" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Desde otro año (ej: experiencia con Rust)
|
|||
|
|
```vue
|
|||
|
|
<YearsCounter :startYear="2019" :speed="100" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Con delay personalizado (iniciar 5 segundos después)
|
|||
|
|
```vue
|
|||
|
|
<YearsCounter :startYear="1985" :speed="80" :delay="5000" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Sin delay (iniciar inmediatamente cuando sea visible)
|
|||
|
|
```vue
|
|||
|
|
<YearsCounter :startYear="1985" :speed="80" :delay="0" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🎨 Personalización del Estilo
|
|||
|
|
|
|||
|
|
El componente usa estas características visuales:
|
|||
|
|
|
|||
|
|
1. **Badge Verde**:
|
|||
|
|
- Gradiente: `rgba(72,187,120,0.2)` → `rgba(72,187,120,0.4)`
|
|||
|
|
- Borde: `#48bb78` (verde Rust accent)
|
|||
|
|
- Bordes redondeados: `15px`
|
|||
|
|
|
|||
|
|
2. **Número**:
|
|||
|
|
- Color: `#4ade80` (verde brillante)
|
|||
|
|
- Tamaño: `48px`
|
|||
|
|
- Fondo oscuro con opacidad
|
|||
|
|
|
|||
|
|
3. **Animaciones**:
|
|||
|
|
- **Pulso del badge**: 3 segundos, brillo entre 0.3 y 0.6 opacidad
|
|||
|
|
- **Flip del número**: 0.3 segundos, scale de `1,1` → `1,0.3` → `1,1`
|
|||
|
|
|
|||
|
|
### 🖼️ Estructura del SVG
|
|||
|
|
|
|||
|
|
```svg
|
|||
|
|
<svg width="300" height="120">
|
|||
|
|
<!-- Badge con gradiente y animación -->
|
|||
|
|
<rect with gradient + glow filter + pulse animation />
|
|||
|
|
|
|||
|
|
<!-- Emoji -->
|
|||
|
|
<text>💻</text>
|
|||
|
|
|
|||
|
|
<!-- Contenedor del número -->
|
|||
|
|
<g>
|
|||
|
|
<rect (fondo oscuro) />
|
|||
|
|
<text (número con animación de flip) />
|
|||
|
|
</g>
|
|||
|
|
|
|||
|
|
<!-- Texto descriptivo -->
|
|||
|
|
<text>años como desarrollador</text>
|
|||
|
|
</svg>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🔄 Cómo Funciona la Animación
|
|||
|
|
|
|||
|
|
#### Detección de Visibilidad (Nuevo)
|
|||
|
|
|
|||
|
|
1. **IntersectionObserver**: Monitorea cuando la slide es visible (30% threshold)
|
|||
|
|
2. **Activación única**: Solo se inicia la primera vez que aparece
|
|||
|
|
3. **Delay configurable**: Espera 3 segundos (por defecto) antes de iniciar
|
|||
|
|
4. **Compatible**: Funciona tanto en Slidev como en navegación normal
|
|||
|
|
|
|||
|
|
#### Secuencia de la Animación
|
|||
|
|
|
|||
|
|
1. **Slide visible**: IntersectionObserver detecta que el componente es visible
|
|||
|
|
2. **Delay**: Espera el tiempo configurado (por defecto 3000ms)
|
|||
|
|
3. **Inicio**: Comienza el contador desde 1
|
|||
|
|
4. **Incremento**: Cada `speed` milisegundos, incrementa el contador
|
|||
|
|
5. **Efecto flip**: Cada cambio de número aplica una animación de scale en el eje Y
|
|||
|
|
6. **Fin**: Cuando alcanza el año objetivo (2025-1985=40), detiene el intervalo
|
|||
|
|
|
|||
|
|
**Ejemplo de timing completo**:
|
|||
|
|
- Llegas a la slide 2 → espera 3 segundos → cuenta de 1 a 40 en 3.2 segundos = **6.2 segundos total**
|
|||
|
|
|
|||
|
|
### 💻 Código JavaScript
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const counterElement = ref(null) // Referencia al elemento DOM
|
|||
|
|
const displayYear = ref(1)
|
|||
|
|
const hasStarted = ref(false) // Prevenir múltiples inicios
|
|||
|
|
const targetYear = new Date().getFullYear() - props.startYear // 2025 - 1985 = 40
|
|||
|
|
|
|||
|
|
const startAnimation = () => {
|
|||
|
|
if (hasStarted.value) return // Solo iniciar una vez
|
|||
|
|
|
|||
|
|
hasStarted.value = true
|
|||
|
|
|
|||
|
|
// Esperar el delay configurado antes de iniciar
|
|||
|
|
setTimeout(() => {
|
|||
|
|
let year = 1
|
|||
|
|
const interval = setInterval(() => {
|
|||
|
|
if (year <= targetYear) {
|
|||
|
|
displayYear.value = year
|
|||
|
|
year++
|
|||
|
|
} else {
|
|||
|
|
clearInterval(interval)
|
|||
|
|
}
|
|||
|
|
}, props.speed)
|
|||
|
|
}, props.delay)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(async () => {
|
|||
|
|
await nextTick()
|
|||
|
|
|
|||
|
|
// Observar cuando el componente es visible
|
|||
|
|
const observer = new IntersectionObserver(
|
|||
|
|
(entries) => {
|
|||
|
|
entries.forEach((entry) => {
|
|||
|
|
if (entry.isIntersecting && !hasStarted.value) {
|
|||
|
|
startAnimation()
|
|||
|
|
observer.disconnect() // Dejar de observar después de iniciar
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
{ threshold: 0.3 } // 30% visible
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
observer.observe(counterElement.value)
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🎬 Resultado
|
|||
|
|
|
|||
|
|
Cuando navegas a la slide:
|
|||
|
|
1. **Espera 3 segundos** (delay configurable)
|
|||
|
|
2. **Inicia en 1**
|
|||
|
|
3. **Va incrementando** cada 80ms
|
|||
|
|
4. **Animación de flip** en cada cambio
|
|||
|
|
5. **Termina en 40** (años desde 1985 hasta 2025)
|
|||
|
|
|
|||
|
|
**Duración total**:
|
|||
|
|
- Delay: 3 segundos
|
|||
|
|
- Animación: 40 años × 80ms = 3.2 segundos
|
|||
|
|
- **Total: 6.2 segundos**
|
|||
|
|
|
|||
|
|
### ✨ Comportamiento Inteligente
|
|||
|
|
|
|||
|
|
- ✅ **Solo se inicia cuando la slide es visible** (no al cargar la presentación)
|
|||
|
|
- ✅ **Delay de 3 segundos** para dar tiempo al usuario a leer el título
|
|||
|
|
- ✅ **Se inicia solo una vez** (aunque vuelvas a la slide, no se reinicia)
|
|||
|
|
- ✅ **Compatible con navegación** hacia adelante y atrás en Slidev
|
|||
|
|
|
|||
|
|
### 📱 Responsive
|
|||
|
|
|
|||
|
|
El SVG se adapta automáticamente al contenedor y mantiene su aspecto.
|
|||
|
|
|
|||
|
|
### 🔄 Actualización Automática
|
|||
|
|
|
|||
|
|
El cálculo `new Date().getFullYear() - props.startYear` se hace al montar el componente, por lo que:
|
|||
|
|
- En **2025**: muestra de 1 a 40
|
|||
|
|
- En **2026**: mostrará de 1 a 41
|
|||
|
|
- Y así sucesivamente
|
|||
|
|
|
|||
|
|
### 🎯 Implementación en el Slide
|
|||
|
|
|
|||
|
|
**Slide 2** - Introducción Personal:
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
---
|
|||
|
|
layout: intro
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Jesús Pérez Lorenzo
|
|||
|
|
|
|||
|
|
<div class="flex items-center gap-11 mt-8">
|
|||
|
|
<div class="leading-8 opacity-80">
|
|||
|
|
|
|||
|
|
<YearsCounter :startYear="1985" :speed="80" />
|
|||
|
|
|
|||
|
|
<span class="orange">Rust</span> Developer & Cloud Architect<br>
|
|||
|
|
...
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🛠️ Modificar la Animación
|
|||
|
|
|
|||
|
|
Para cambiar el tipo de animación, edita la sección `<animateTransform>` en el componente:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- Flip horizontal en lugar de vertical -->
|
|||
|
|
<animateTransform
|
|||
|
|
attributeName="transform"
|
|||
|
|
type="scale"
|
|||
|
|
values="1,1; 0.3,1; 1,1"
|
|||
|
|
dur="0.3s"/>
|
|||
|
|
|
|||
|
|
<!-- Rotación -->
|
|||
|
|
<animateTransform
|
|||
|
|
attributeName="transform"
|
|||
|
|
type="rotate"
|
|||
|
|
values="0 120 75; 360 120 75"
|
|||
|
|
dur="0.5s"/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 🎨 Cambiar Colores
|
|||
|
|
|
|||
|
|
Para cambiar el esquema de colores, modifica:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- Verde → Naranja Rust -->
|
|||
|
|
<stop offset="0%" style="stop-color:rgba(247,76,0,0.2)" />
|
|||
|
|
<stop offset="100%" style="stop-color:rgba(247,76,0,0.4)" />
|
|||
|
|
|
|||
|
|
<!-- Color del número -->
|
|||
|
|
<text fill="#ff6b4a">
|
|||
|
|
|
|||
|
|
<!-- Color del borde -->
|
|||
|
|
<rect stroke="#f74c00" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚀 Crear Otros Contadores
|
|||
|
|
|
|||
|
|
Puedes reutilizar el componente para otras métricas:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<!-- Años con Rust -->
|
|||
|
|
<YearsCounter :startYear="2019" :speed="100" />
|
|||
|
|
|
|||
|
|
<!-- Años en Cloud -->
|
|||
|
|
<YearsCounter :startYear="2013" :speed="100" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📦 Archivos del Componente
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
slides/
|
|||
|
|
├── components/
|
|||
|
|
│ ├── YearsCounter.vue ← El componente
|
|||
|
|
│ └── README.md ← Esta documentación
|
|||
|
|
└── slides.md ← Uso del componente
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔍 Troubleshooting
|
|||
|
|
|
|||
|
|
### El componente no se muestra
|
|||
|
|
- Asegúrate de que el archivo esté en `slides/components/`
|
|||
|
|
- Slidev detecta automáticamente componentes en esta carpeta
|
|||
|
|
|
|||
|
|
### La animación es muy rápida/lenta
|
|||
|
|
- Ajusta el prop `:speed` (en milisegundos)
|
|||
|
|
- Valores recomendados: 50-150ms
|
|||
|
|
|
|||
|
|
### El número no llega al año correcto
|
|||
|
|
- Verifica que `startYear` sea correcto
|
|||
|
|
- El cálculo es: `new Date().getFullYear() - startYear`
|