ライブデモ Live Demo

テキストが単語ごとに下からスライドして表示されます。各単語の表示タイミングはCSSの`animation-delay`で制御します。

Text appears word by word, sliding up from below. Each word's reveal timing is controlled via CSS `animation-delay`.

Beautiful text animations made simple
0.8s
0.2s

AI向け説明 AI Description

このパターンは、テキストを単語ごとに下からスライドアップして表示するアニメーションです。

  • 基本構造: テキストを単語ごとに `<span class="reveal-word"><span>単語</span></span>` でラップ
  • アニメーション: `transform: translateY()` を使って下から上にスライド
  • タイミング制御: `animation-delay` で各単語の表示タイミングを調整
  • カスタマイズ可能項目: アニメーション時間、単語間の間隔、スライド距離、色

This pattern displays text by sliding words up from below in sequence.

  • Basic Structure: Wrap each word with `<span class="reveal-word"><span>word</span></span>`
  • Animation: Uses `transform: translateY()` to slide from bottom to top
  • Timing Control: `animation-delay` adjusts each word's reveal timing
  • Customizable: Animation duration, word delay, slide distance, colors

調整可能パラメータ Adjustable Parameters

実装コード Implementation Code

HTML + CSS

<div class="text-reveal">
  <span class="reveal-word"><span>Beautiful</span></span>
  <span class="reveal-word"><span>text</span></span>
  <span class="reveal-word"><span>animations</span></span>
</div>

<style>
.text-reveal {
  font-size: 48px;
  font-weight: 700;
  color: #ffffff;
}

.reveal-word {
  display: inline-block;
  overflow: hidden;
  margin: 0 0.1em;
}

.reveal-word span {
  display: inline-block;
  transform: translateY(60px);
  opacity: 0;
  animation: slideReveal 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
}

@keyframes slideReveal {
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.reveal-word:nth-child(1) span { animation-delay: 0s; }
.reveal-word:nth-child(2) span { animation-delay: 0.2s; }
.reveal-word:nth-child(3) span { animation-delay: 0.4s; }
</style>

React (JSX + CSS)

// react/M-005.jsx
import React, { useState, useRef } from 'react';
import './M-005.css';

const TextRevealSlide = ({ 
  text = "Beautiful text animations made simple",
  duration = 0.8,
  delay = 0.2,
  distance = 60,
  className = "",
  onAnimationComplete 
}) => {
  const [isAnimating, setIsAnimating] = useState(false);
  const containerRef = useRef(null);

  const words = text.split(' ');

  const replay = () => {
    if (isAnimating) return;
    
    setIsAnimating(true);
    const wordElements = containerRef.current?.querySelectorAll('.reveal-word span');
    
    wordElements?.forEach((word) => {
      word.style.animation = 'none';
      word.offsetHeight; // trigger reflow
      word.style.animation = '';
    });

    // Reset animation state after animation completes
    const totalDuration = (words.length * delay + duration) * 1000;
    setTimeout(() => {
      setIsAnimating(false);
      onAnimationComplete?.();
    }, totalDuration);
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      replay();
    }
  };

  return (
    <div className={`text-reveal-container ${className}`}>
      <div 
        ref={containerRef}
        className="text-reveal" 
        style={{
          '--reveal-duration': `${duration}s`,
          '--reveal-delay': `${delay}s`,
          '--reveal-distance': `${distance}px`
        }}
      >
        {words.map((word, index) => (
          <span key={`${word}-${index}`} className="reveal-word">
            <span style={{ animationDelay: `${index * delay}s` }}>
              {word}
            </span>
          </span>
        ))}
      </div>
      <button 
        className="replay-button"
        onClick={replay}
        onKeyDown={handleKeyDown}
        disabled={isAnimating}
        aria-label="Replay animation"
      >
        {isAnimating ? 'Playing...' : 'Replay'}
      </button>
    </div>
  );
};

// Usage example component
const TextRevealSlideDemo = () => {
  const [settings, setSettings] = useState({
    duration: 0.8,
    delay: 0.2,
    distance: 60,
    text: "Beautiful text animations made simple"
  });

  const handleSettingChange = (key, value) => {
    setSettings(prev => ({
      ...prev,
      [key]: value
    }));
  };

  return (
    <div className="text-reveal-demo">
      <div className="demo-stage">
        <TextRevealSlide 
          text={settings.text}
          duration={settings.duration}
          delay={settings.delay}
          distance={settings.distance}
          onAnimationComplete={() => console.log('Animation completed!')}
        />
      </div>
      
      <div className="controls">
        <div className="control-group">
          <label htmlFor="duration-slider">
            Animation Duration: {settings.duration.toFixed(1)}s
          </label>
          <input
            id="duration-slider"
            type="range"
            min="0.3"
            max="1.5"
            step="0.1"
            value={settings.duration}
            onChange={(e) => handleSettingChange('duration', parseFloat(e.target.value))}
          />
        </div>
        
        <div className="control-group">
          <label htmlFor="delay-slider">
            Word Delay: {settings.delay.toFixed(2)}s
          </label>
          <input
            id="delay-slider"
            type="range"
            min="0.1"
            max="0.5"
            step="0.05"
            value={settings.delay}
            onChange={(e) => handleSettingChange('delay', parseFloat(e.target.value))}
          />
        </div>
        
        <div className="control-group">
          <label htmlFor="distance-slider">
            Slide Distance: {settings.distance}px
          </label>
          <input
            id="distance-slider"
            type="range"
            min="20"
            max="100"
            step="10"
            value={settings.distance}
            onChange={(e) => handleSettingChange('distance', parseInt(e.target.value))}
          />
        </div>
        
        <div className="control-group">
          <label htmlFor="text-input">
            Text Content:
          </label>
          <input
            id="text-input"
            type="text"
            value={settings.text}
            onChange={(e) => handleSettingChange('text', e.target.value)}
            placeholder="Enter your text here"
          />
        </div>
      </div>
    </div>
  );
};

export default TextRevealSlide;
export { TextRevealSlideDemo };
/* react/M-005.css */
/* M-005 Text Reveal Slide Styles */

.text-reveal-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
}

.text-reveal {
  font-size: clamp(28px, 5vw, 48px);
  font-weight: 700;
  color: #ffffff;
  line-height: 1.3;
  text-align: center;
  max-width: 600px;
  
  /* CSS Custom Properties for customization */
  --reveal-duration: 0.8s;
  --reveal-delay: 0.2s;
  --reveal-distance: 60px;
}

.reveal-word {
  display: inline-block;
  overflow: hidden;
  margin: 0 0.1em;
  vertical-align: bottom;
}

.reveal-word span {
  display: inline-block;
  transform: translateY(var(--reveal-distance));
  opacity: 0;
  animation: slideReveal var(--reveal-duration) cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
  text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}

@keyframes slideReveal {
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

/* Demo Stage Styles */
.text-reveal-demo {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  padding: 2rem;
}

.demo-stage {
  border-radius: 18px;
  padding: 48px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border: 1px solid rgba(255, 255, 255, 0.1);
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  margin-bottom: 2rem;
}

.replay-button {
  padding: 0.75rem 1.5rem;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
  font-size: 1rem;
}

.replay-button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}

.replay-button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.replay-button:focus {
  outline: 2px solid #667eea;
  outline-offset: 2px;
}

/* Controls Styles */
.controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
  padding: 1.5rem;
  background: #f8fafc;
  border-radius: 12px;
  border: 1px solid #e2e8f0;
}

.control-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.control-group label {
  font-weight: 600;
  color: #374151;
  font-size: 0.875rem;
}

.control-group input[type="range"] {
  width: 100%;
  height: 6px;
  background: #e2e8f0;
  border-radius: 3px;
  outline: none;
  cursor: pointer;
}

.control-group input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 18px;
  height: 18px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  cursor: pointer;
  border: 2px solid white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.control-group input[type="range"]::-moz-range-thumb {
  width: 18px;
  height: 18px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  cursor: pointer;
  border: 2px solid white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.control-group input[type="text"] {
  padding: 0.75rem;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 1rem;
  transition: border-color 0.2s ease;
}

.control-group input[type="text"]:focus {
  outline: none;
  border-color: #667eea;
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

/* Dark theme support */
@media (prefers-color-scheme: dark) {
  .controls {
    background: #1f2937;
    border-color: #374151;
  }
  
  .control-group label {
    color: #f3f4f6;
  }
  
  .control-group input[type="text"] {
    background: #374151;
    border-color: #4b5563;
    color: #f3f4f6;
  }
  
  .control-group input[type="text"]:focus {
    border-color: #667eea;
  }
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .text-reveal-demo {
    padding: 1rem;
  }
  
  .demo-stage {
    padding: 24px;
    min-height: 150px;
  }
  
  .controls {
    grid-template-columns: 1fr;
    gap: 1rem;
  }
}

/* High contrast mode support */
@media (prefers-contrast: high) {
  .demo-stage {
    border: 2px solid #000;
  }
  
  .replay-button {
    border: 2px solid #000;
  }
  
  .control-group input[type="range"]::-webkit-slider-thumb {
    border: 3px solid #000;
  }
}

AIへの指示テンプレート AI Prompt Template

以下のテンプレートをコピーしてAIアシスタントに貼り付けると、このパターンの実装を依頼できます。 Copy the template below and paste it into your AI assistant to request an implementation of this pattern.