View Transition:MPA 範例
日期:2025-05-19
| 應用案例
6. 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>© 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>© 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>© 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)
- 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>→</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>→</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 發表的文章,欲了解更多詳情,歡迎點擊連結前往閱讀。