# 科技新知

View Transition:MPA 範例

作者:Oliver Xiong

日期:2025-05-19

| 應用案例

6. Page Transition: Slide (MPA)

Page Transition: Slide (MPA)

Page Transition: Slide (MPA)

  • HTML (index.html)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" href="styles.css" />
        <script src="script.js"></script>
        <title>Home - My Website</title>
      </head>
      <body>
        <header>
          <nav class="main-nav">
            <div class="logo">
              <h1>MyWebsite</h1>
            </div>
            <ul class="nav-links">
              <li class="active"><a href="index.html">Home</a></li>
              <li><a href="about.html">About</a></li>
              <li><a href="contact.html">Contact</a></li>
            </ul>
          </nav>
        </header>
    
        <main class="container home">
          <section class="hero">
            <div class="hero-content">
              <h2>Welcome to Our Website</h2>
              <p>Explore our various services and products</p>
              <button class="cta-button">Learn More</button>
            </div>
            <div class="hero-image">
              <img src="/images/welcome.webp" alt="Home hero image" />
            </div>
          </section>
    
          <section class="features">
            <div class="feature-card">
              <img src="/images/service.webp" alt="Feature image" />
              <h3>Quality Service</h3>
              <p>We provide the highest quality services to meet all your needs.</p>
            </div>
            <div class="feature-card">
              <img src="/images/team.webp" alt="Feature image" />
              <h3>Professional Team</h3>
              <p>Our team consists of experienced professionals in the field.</p>
            </div>
            <div class="feature-card">
              <img src="/images/tech.webp" alt="Feature image" />
              <h3>Innovative Technology</h3>
              <p>We constantly pursue innovation to bring you the latest technological experiences.</p>
            </div>
          </section>
        </main>
    
        <footer>
          <p>&copy; 2025 MyWebsite. All rights reserved.</p>
        </footer>
      </body>
    </html>
    
  • HTML (about.html)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" href="styles.css" />
        <script src="script.js"></script>
        <title>About - My Website</title>
      </head>
      <body>
        <header>
          <nav class="main-nav">
            <div class="logo">
              <h1>MyWebsite</h1>
            </div>
            <ul class="nav-links">
              <li><a href="index.html">Home</a></li>
              <li class="active"><a href="about.html">About</a></li>
              <li><a href="contact.html">Contact</a></li>
            </ul>
          </nav>
        </header>
    
        <main class="container about">
          <div class="about-header">
            <h2>About Us</h2>
            <div class="divider"></div>
          </div>
    
          <div class="about-content">
            <div class="about-image">
              <img src="/images/menbers.webp" alt="About us image" />
            </div>
            <div class="about-text">
              <h3>Our Story</h3>
              <p>We are a company established in 2010, focused on providing high-quality services and products. Over the years, we have continuously grown and innovated, becoming a leader in our industry.</p>
    
              <h3>Our Mission</h3>
              <p>Our mission is to create value for our clients through innovation and quality service, while making a positive contribution to society.</p>
    
              <h3>Our Vision</h3>
              <p>To become an industry leader and continue to drive the development and progress of the industry.</p>
            </div>
          </div>
    
          <div class="team-section">
            <h3>Our Team</h3>
            <div class="team-members">
              <div class="member">
                <img src="/images/menber-1.webp" alt="Team member" />
                <h4>Carrie Kelley</h4>
                <p>Founder & CEO</p>
              </div>
              <div class="member">
                <img src="/images/menber-2.webp" alt="Team member" />
                <h4>Roxanne Hawkins</h4>
                <p>Technical Director</p>
              </div>
              <div class="member">
                <img src="/images/menber-3.webp" alt="Team member" />
                <h4>Cory Swanson</h4>
                <p>Marketing Director</p>
              </div>
            </div>
          </div>
        </main>
    
        <footer>
          <p>&copy; 2025 MyWebsite. All rights reserved.</p>
        </footer>
      </body>
    </html>
    
  • HTML (contact.html)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" href="styles.css" />
        <script src="script.js"></script>
        <title>Contact - My Website</title>
      </head>
      <body>
        <header>
          <nav class="main-nav">
            <div class="logo">
              <h1>MyWebsite</h1>
            </div>
            <ul class="nav-links">
              <li><a href="index.html">Home</a></li>
              <li><a href="about.html">About</a></li>
              <li class="active"><a href="contact.html">Contact</a></li>
            </ul>
          </nav>
        </header>
    
        <main class="container contact">
          <div class="contact-info">
            <h2>Contact Us</h2>
            <p>We are happy to answer your questions and provide more information. Please fill out the form below or contact us directly using the provided contact methods.</p>
    
            <div class="contact-methods">
              <div class="contact-method">
                <div class="icon">📍</div>
                <div class="details">
                  <h4>Address</h4>
                  <p>7 Xinyi Road Section 5, Xinyi District, Taipei</p>
                </div>
              </div>
    
              <div class="contact-method">
                <div class="icon">📞</div>
                <div class="details">
                  <h4>Phone</h4>
                  <p>+886 2 1234 5678</p>
                </div>
              </div>
    
              <div class="contact-method">
                <div class="icon">✉️</div>
                <div class="details">
                  <h4>Email</h4>
                  <p>info@mywebsite.com</p>
                </div>
              </div>
            </div>
          </div>
    
          <div class="contact-form-container">
            <form class="contact-form">
              <h3>Send Message</h3>
    
              <div class="form-group">
                <label for="name">Name</label>
                <input type="text" id="name" name="name" required />
              </div>
    
              <div class="form-group">
                <label for="email">Email</label>
                <input type="email" id="email" name="email" required />
              </div>
    
              <div class="form-group">
                <label for="subject">Subject</label>
                <input type="text" id="subject" name="subject" required />
              </div>
    
              <div class="form-group">
                <label for="message">Message</label>
                <textarea id="message" name="message" rows="5" required></textarea>
              </div>
    
              <button type="submit" class="submit-button">Submit</button>
            </form>
          </div>
    
          <div class="map-container">
            <h3>Our Location</h3>
            <div class="map">
              <img src="/images/location.webp" alt="Map" />
            </div>
          </div>
        </main>
    
        <footer>
          <p>&copy; 2025 MyWebsite. All rights reserved.</p>
        </footer>
      </body>
    </html>
    
  • CSS (SCSS)
    $primary-color: #3498db;
    $secondary-color: #2c3e50;
    $accent-color: #e74c3c;
    $light-color: #ecf0f1;
    $dark-color: #2c3e50;
    $text-color: #333;
    $font-primary: 'Arial', sans-serif;
    $font-secondary: 'Georgia', serif;
    $transition-speed: 0.3s;
    $border-radius: 5px;
    
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      background-color: $light-color;
      color: $text-color;
      font-family: $font-primary;
      line-height: 1.5;
    
      a {
        color: inherit;
        text-decoration: none;
      }
    
      ul {
        list-style: none;
      }
    
      header {
        padding: 1rem 0;
        background-color: $dark-color;
        color: white;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        view-transition-name: header; // 設定 view-transition-name
    
        .main-nav {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 0 2rem;
    
          .logo {
            h1 {
              color: white;
              font-size: 1.8rem;
              font-weight: bold;
            }
          }
    
          .nav-links {
            display: flex;
            gap: 2rem;
    
            li {
              position: relative;
    
              a {
                padding: 0.5rem 0;
                transition: color $transition-speed;
    
                &:hover {
                  color: $primary-color;
                }
              }
    
              &.active {
                a {
                  color: $primary-color;
    
                  &::after {
                    content: '';
                    position: absolute;
                    bottom: -5px;
                    left: 0;
                    width: 100%;
                    height: 3px;
                    background-color: $primary-color;
                  }
                }
              }
            }
          }
        }
      }
    
      .container {
        margin: 0 auto;
        padding: 2rem;
        max-width: 1200px;
    
        &.home {
          .hero {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 3rem;
    
            .hero-content {
              flex: 1;
              padding-right: 2rem;
    
              h2 {
                font-size: 2.5rem;
                margin-bottom: 1rem;
                color: $secondary-color;
              }
    
              p {
                font-size: 1.2rem;
                margin-bottom: 1.5rem;
                color: lighten($text-color, 20%);
              }
    
              .cta-button {
                background-color: $primary-color;
                color: white;
                border: none;
                padding: 0.8rem 1.5rem;
                font-size: 1rem;
                border-radius: $border-radius;
                cursor: pointer;
                transition: background-color $transition-speed;
    
                &:hover {
                  background-color: darken($primary-color, 10%);
                }
              }
            }
    
            .hero-image {
              flex: 1;
    
              img {
                width: 100%;
                border-radius: $border-radius;
                box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
              }
            }
          }
    
          .features {
            display: flex;
            justify-content: space-between;
            gap: 2rem;
    
            .feature-card {
              flex: 1;
              background-color: white;
              border-radius: $border-radius;
              padding: 1.5rem;
              box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
              transition: transform $transition-speed;
    
              &:hover {
                transform: translateY(-5px);
              }
    
              img {
                width: 100%;
                border-radius: $border-radius;
                margin-bottom: 1rem;
              }
    
              h3 {
                font-size: 1.3rem;
                margin-bottom: 0.5rem;
                color: $secondary-color;
              }
    
              p {
                color: lighten($text-color, 20%);
              }
            }
          }
        }
    
        &.about {
          .about-header {
            text-align: center;
            margin-bottom: 3rem;
    
            h2 {
              font-size: 2.5rem;
              color: $secondary-color;
              margin-bottom: 1rem;
              font-family: $font-secondary;
            }
    
            .divider {
              height: 3px;
              width: 100px;
              background-color: $primary-color;
              margin: 0 auto;
            }
          }
    
          .about-content {
            display: flex;
            gap: 3rem;
            margin-bottom: 3rem;
    
            .about-image {
              flex: 1;
    
              img {
                width: 100%;
                border-radius: $border-radius;
                box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
              }
            }
    
            .about-text {
              flex: 1;
    
              h3 {
                font-size: 1.5rem;
                color: $secondary-color;
                margin-bottom: 1rem;
                margin-top: 1.5rem;
    
                &:first-child {
                  margin-top: 0;
                }
              }
    
              p {
                margin-bottom: 1rem;
                line-height: 1.7;
              }
            }
          }
    
          .team-section {
            h3 {
              font-size: 1.8rem;
              color: $secondary-color;
              text-align: center;
              margin-bottom: 2rem;
            }
    
            .team-members {
              display: flex;
              justify-content: space-around;
    
              .member {
                text-align: center;
    
                img {
                  width: 200px;
                  height: 200px;
                  border-radius: 50%;
                  object-fit: cover;
                  margin-bottom: 1rem;
                  border: 3px solid $primary-color;
                }
    
                h4 {
                  font-size: 1.2rem;
                  margin-bottom: 0.3rem;
                  color: $secondary-color;
                }
    
                p {
                  color: lighten($text-color, 20%);
                }
              }
            }
          }
        }
    
        &.contact {
          .contact-info {
            background-color: white;
            padding: 2rem;
            border-radius: $border-radius;
            margin-bottom: 2rem;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
    
            h2 {
              font-size: 2rem;
              color: $secondary-color;
              margin-bottom: 1rem;
            }
    
            p {
              margin-bottom: 2rem;
              max-width: 600px;
            }
    
            .contact-methods {
              display: flex;
              flex-wrap: wrap;
              gap: 2rem;
    
              .contact-method {
                display: flex;
                align-items: flex-start;
    
                .icon {
                  font-size: 1.5rem;
                  margin-right: 1rem;
                  color: $primary-color;
                }
    
                .details {
                  h4 {
                    margin-bottom: 0.3rem;
                    color: $secondary-color;
                  }
                }
              }
            }
          }
    
          .contact-form-container {
            background-color: white;
            padding: 2rem;
            border-radius: $border-radius;
            margin-bottom: 2rem;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
    
            .contact-form {
              max-width: 600px;
              margin: 0 auto;
    
              h3 {
                font-size: 1.5rem;
                color: $secondary-color;
                margin-bottom: 1.5rem;
                text-align: center;
              }
    
              .form-group {
                margin-bottom: 1.5rem;
    
                label {
                  display: block;
                  margin-bottom: 0.5rem;
                  font-weight: bold;
                  color: $secondary-color;
                }
    
                input,
                textarea {
                  width: 100%;
                  padding: 0.8rem;
                  border: 1px solid #ddd;
                  border-radius: $border-radius;
                  font-family: $font-primary;
    
                  &:focus {
                    outline: none;
                    border-color: $primary-color;
                  }
                }
              }
    
              .submit-button {
                background-color: $primary-color;
                color: white;
                border: none;
                padding: 0.8rem 1.5rem;
                font-size: 1rem;
                border-radius: $border-radius;
                cursor: pointer;
                transition: background-color $transition-speed;
                display: block;
                width: 100%;
    
                &:hover {
                  background-color: darken($primary-color, 10%);
                }
              }
            }
          }
    
          .map-container {
            h3 {
              font-size: 1.5rem;
              color: $secondary-color;
              margin-bottom: 1rem;
            }
    
            .map {
              border-radius: $border-radius;
              overflow: hidden;
    
              img {
                width: 100%;
                display: block;
              }
            }
          }
        }
      }
    
      footer {
        margin-top: 2rem;
        padding: 1.5rem 0;
        background-color: $dark-color;
        color: white;
        text-align: center;
      }
    }
    
    @media (max-width: 768px) {
      .main-nav {
        flex-direction: column;
        gap: 1rem;
    
        .logo {
          margin-bottom: 1rem;
        }
      }
    
      .container {
        &.home {
          .hero {
            flex-direction: column;
    
            .hero-content {
              padding-right: 0;
              margin-bottom: 2rem;
            }
          }
    
          .features {
            flex-direction: column;
          }
        }
    
        &.about {
          .about-content {
            flex-direction: column;
          }
    
          .team-section {
            .team-members {
              flex-direction: column;
              gap: 2rem;
            }
          }
        }
    
        &.contact {
          .contact-methods {
            flex-direction: column;
          }
        }
      }
    }
    
    @view-transition {
      navigation: auto;
    }
    
    ::view-transition-group(*) {
      animation-duration: 0.5s;
      animation-timing-function: ease-in-out;
    }
    
    [data-direction='forward']::view-transition-old(root) {
      animation-name: slide-out-left;
    }
    
    [data-direction='forward']::view-transition-new(root) {
      animation-name: slide-in-right;
    }
    
    [data-direction='backward']::view-transition-old(root) {
      animation-name: slide-out-right;
    }
    
    [data-direction='backward']::view-transition-new(root) {
      animation-name: slide-in-left;
    }
    
    @keyframes slide-out-left {
      from {
        transform: translateX(0);
      }
      to {
        transform: translateX(-100%);
      }
    }
    
    @keyframes slide-in-right {
      from {
        transform: translateX(100%);
      }
      to {
        transform: translateX(0);
      }
    }
    
    @keyframes slide-out-right {
      from {
        transform: translateX(0);
      }
      to {
        transform: translateX(100%);
      }
    }
    
    @keyframes slide-in-left {
      from {
        transform: translateX(-100%);
      }
      to {
        transform: translateX(0);
      }
    }
    
  • JavaScript
    const pages = ['index.html', 'about.html', 'contact.html'];
    
    // 根據 from / to 頁面取得動畫方向
    const getAnimationDirection = (from, to) => {
      const fromPath = new URL(from).pathname.split('/').pop();
      const toPath = new URL(to).pathname.split('/').pop();
    
      const fromIndex = pages.indexOf(fromPath);
      const toIndex = pages.indexOf(toPath);
    
      // 上一頁
      if (toIndex === -1) return 'forward';
    
      return fromIndex < toIndex ? 'forward' : 'backward';
    };
    
    // 監聽 pagereveal 事件
    window.addEventListener('pagereveal', async (e) => {
      if (!e.viewTransition) return;
    
      const from = navigation.activation.from.url;
      const to = navigation.activation.entry.url;
    
      // 取得動畫方向
      const direction = getAnimationDirection(from, to);
    
      // 為 html 元素設置 data-direction 並賦值 direction
      document.documentElement.dataset.direction = direction;
    
      await e.viewTransition.finished;
    
      delete document.documentElement.dataset.direction;
    });
    

7. Page Transition: Circle (MPA)

Page Transition: Circle (MPA)

Page Transition: Circle (MPA)

  • HTML (index.html)
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>View Transitions: Circle (MPA, Page 1)</title>
        <link rel="stylesheet" href="styles.css" />
        <script src="script.js"></script>
      </head>
      <body>
        <div class="container">
          <h1>This is view 1</h1>
          <p>Transitions will start from the center of the screen</p>
          <a href="index2.html">Go to View 2 <span>&rarr;</span></a>
        </main>
      </body>
    </html>
    
  • HTML (index2.html)
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>View Transitions: Circle (MPA, Page 1)</title>
      <link rel="stylesheet" href="styles.css" />
      <script src="script.js"></script>
    </head>
    <body>
      <div class="container">
        <h1>This is view 2</h1>
        <p>Transitions will start from the center of the screen</p>
        <a href="index.html">Go to View 1 <span>&rarr;</span></a>
      </main>
    </body>
    </html>
    
  • CSS (SCSS)
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    html {
      background-color: gainsboro;
      user-select: none;
    
      body {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100dvh;
        color: #333;
        text-align: center;
    
        .container {
          display: flex;
          flex-direction: column;
          align-items: center;
          gap: 1rem;
          padding: 2rem;
          border-radius: 1rem;
          background-color: #fff;
          box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
    
          a {
            padding: 0.5rem 1rem;
            width: fit-content;
            border-radius: 0.5rem;
            background-color: #eee;
            color: #333;
            text-decoration: none;
    
            &:hover {
              background-color: #666;
              color: #fff;
            }
          }
        }
      }
    }
    
    // 設定轉場的導航模式為自動(當頁面間導航時自動應用轉場效果)
    @view-transition {
      navigation: auto;
    }
    
    // 為新舊畫面的根元素設定樣式
    ::view-transition-old(root),
    ::view-transition-new(root) {
      display: block;
      mix-blend-mode: normal;
      animation: none; // 停用瀏覽器預設轉場動畫,在 JS 中自訂
    }
    
  • JavaScript
    // 隨機整數
    const randomInteger = (min, max) =>
      Math.floor(Math.random() * (max - min + 1)) + min;
    
    // 隨機顏色
    const randomColor = () => randomInteger(0, 16777215).toString(16);
    
    const randomBgColor = `#${randomColor()}`;
    
    // 計算畫面正中間位置(轉場開始處)
    const centerX = window.innerWidth / 2;
    const centerY = window.innerHeight / 2;
    
    // 監聽 pageswap 事件
    window.addEventListener('pageswap', (e) => {
      if (e.viewTransition) {
        // 儲存當前背景顏色
        const currentBgColor = getComputedStyle(
          document.documentElement
        ).backgroundColor;
        sessionStorage.setItem('lastBgColor', currentBgColor);
      }
    });
    
    // 監聽 pagereveal 事件
    window.addEventListener('pagereveal', async (e) => {
      // 檢查是否支援 View Transition
      if (!e.viewTransition) return;
    
      // 將 html 的背景色設為一個隨機顏色
      document.documentElement.style.backgroundColor = randomBgColor;
    
      // 計算轉場動畫的最大半徑,確保圓形動畫在展開時能夠覆蓋到整個頁面的每一個角落
      // Math.hypot(x, y) 會回傳 x 的平方加上 y 的平方後開根號的結果,相當於計算直角三角形的斜邊長度
      const endRadius = Math.hypot(
        Math.max(centerX, window.innerWidth - centerX),
        Math.max(centerY, window.innerHeight - centerY)
      );
    
      // 等待轉場準備就緒
      await e.viewTransition.ready;
    
      // 為 HTML 根元素創建圓形展開動畫
      document.documentElement.animate(
        // keyframes >>> 此處為物件形式
        {
          clipPath: [
            `circle(0 at ${centerX}px ${centerY}px)`, // from
            `circle(${endRadius}px at ${centerX}px ${centerY}px)`, // to
          ],
        },
        // options
        {
          duration: 500,
          easing: 'ease-in',
          pseudoElement: '::view-transition-new(root)', // 要應用動畫的偽元素 >>> 此處為新畫面的根元素
        }
      );
    });
    

本文轉載自筆者於 Medium 發表的文章,欲了解更多詳情,歡迎點擊連結前往閱讀。

更多資訊公告