TTOANN

901 lượt xem

Page Transition With Server Side Rendering

  • #Gsap
  • #SSR
  • #Jquery
5 phút đọc

demo

Khi xem demo ở phía trên mọi người thường nghĩ là chỉ những website CSR (Client Side Rendering) mới có thể chuyển trang như vậy thôi đúng không ?
Nhưng hôm nay như tiêu đề chúng ta sẽ hiện thực nó các ở website SSR (Server Side Rendering) luôn nhé 😼.

# Bắt đầu.

Chúng ta cần gì ?

  1. Jquery (quá quen thuộc rồi)
  2. Gsap (thư viện về animation cực kì mạnh mẽ)

Trước hết chúng ta sẽ có một source đơn giản thôi.

├── images
│   └── xxxxxx
├── main.js
├── style.css
└── index.html
└── about.html
└── product.html

Các page sẽ có chung cấu trúc như sau:


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><!-- Tên trang --></title>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <main class="wrapper">

    <div class="content">
        <!-- Nội dung -->
    </div>

    <ul class="nav-list">
      <li><a href="/">Trang chủ</a></li>
      <li><a class="active" href="about.html">Về chúng tôi</a></li>
      <li><a href="product.html">Sản phẩm</a></li>
    </ul>

    <div class="overlay"></div>

  </main>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="main.js"></script>

</html>


Phần css đơn giản một chút cho đời thêm đẹp

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  min-height: 100vh;
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  -ms-overflow-style: none;
  scrollbar-width: none;
}

html::-webkit-scrollbar,
body::-webkit-scrollbar {
  display: none;
}

html.transition,
.transtion body {
  overflow: hidden;
}

.overlay {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  opacity: 0;
  z-index: 1;
  background: rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

.nav-list {
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
  bottom: calc(env(safe-area-inset-bottom) + 2rem);
  left: 50%;
  transform: translateX(-50%);
  list-style: none;
  padding: 1rem 1.6rem;
  border-radius: 3rem;
  background: #fff;
  border: 1px solid #232323;
  z-index: 99;
  transition: 0.3s;
}

.nav-list li:nth-child(2) {
  margin: 0 1rem;
}

.nav-list a {
  text-decoration: none;
  display: block;
  text-transform: uppercase;
  font-size: 0.8rem;
  color: #232323;
  position: relative;
  width: fit-content;
  min-width: max-content;
}

.nav-list a::before {
  content: "";
  position: absolute;
  bottom: -2px;
  left: 0;
  right: 0;
  height: 1px;
  background: #232323;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.6s;
}

.nav-list a.active::before,
.nav-list a:hover::before {
  transform: scaleX(1);
  transform-origin: left;
}

.content {
  background: #fff;
  min-height: 100vh;
}
.content-inner {
  padding: 0.5rem 0.5rem 5rem;
}

.content h1 {
  line-height: 1.3;
  font-size: 4rem;
  margin-bottom: 1rem;
}

.content h3 {
  margin: 0.5rem 0;
}

.content ul {
  margin-left: 2rem;
  margin-bottom: 1rem;
}

.content p {
  max-width: 80%;
  font-size: 1rem;
  margin-left: 3rem;
  padding-bottom: 1.5rem;
}

.content img {
  display: block;
  width: auto;
  height: auto;
  object-fit: cover;
  max-width: 100%;
  margin: 0 auto;
  border-radius: 0.5rem;
  overflow: hidden;
}

.content.sec {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 4;
}

.grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.5rem;
}

.grid .grid-item {
  text-align: center;
  position: relative;
}

@media screen and (min-width: 768px) {

  .nav-list {
    padding: 1.5rem 4rem;
    width: auto;
  }

  .nav-list li:nth-child(2) {
    margin: 0 3rem;
  }

  .grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
  }

  .content {
    padding: 2rem;

    padding-bottom: 9rem;
  }

  .content h1 {
    font-size: 7rem;
  }

  .content p {
    font-size: 2rem;
  }
}

# Logic

Về phần logic cũng khá đơn giản như đang giỡn vậy thôi. Mỗi khi user click vào đường dẫn qua trang khác, chúng ta sẽ gọi ajax với trang đó, lấy nội dung content rồi append vào trang hiện có và thực hiện animation blalalala, khi animation chạy xong thì redirect qua trang đó thôi.

# Bắt tay vào code.

Ở file main.js chúng ta sẽ viết như này.

Trước tiên khi page được load, chúng ta sẽ tạo một biến là nextLink dùng để lưu đường dẫn của trang mà user click vào.

Và thêm class first cho content của trang hiện tại để phân biệt với content của trang được ajax load vào.

let nextLink
$(".content").addClass("first")

Tiếp theo chúng ta sẽ tạo một function để thực hiện animation với Gsap, khá đơn giản.

function pageTransition() {
  $("html").addClass("transition")
  let tl = gsap.timeline({
    onComplete: updatePage
  })
  tl.from(".content.sec", {
    y: "110vh",
    delay: 0.2,
    duration: 0.8,
    ease: "power4.out"
  })
  tl.to(
    ".overlay",
    {
      opacity: 1,
      duration: 0.3,
      ease: "power1.out"
    },
    0
  )
  tl.to(
    ".content.first",
    {
      scale: 0.95,
      duration: 0.3,
      ease: "power1.out"
    },
    0
  )
}

Tiếp tục tạo một function để update lại link khi thực hiện xong animation. Lại càng đơn giản nữa.

function updatePage() {
  window.location = nextLink
}

Chúng ta sẽ bắt sự kiện khi user click vào menu và thực hiện gọi ajax để lấy html của trang đó rồi append vào trang hiện tại. Khi hoàn thành thì gọi đến function pageTransition ở trên là xong.

$(".nav-list a:not(.active)").on("click", function (e) {
  e.preventDefault()
  nextLink = this.href
  $.ajax({
    url: this.href,
    success: function (data) {
      let el = $(data).find(".content").addClass("sec")
      $(".wrapper").append(el)
    },
    complete: function () {
      pageTransition()
    }
  })
})

Cuối cùng main.js sẽ như sau.

let nextLink

$(".content").addClass("first")

function updatePage() {
  window.location = nextLink
}

function pageTransition() {
  $("html").addClass("transition")
  let tl = gsap.timeline({
    onComplete: updatePage
  })
  tl.from(".content.sec", {
    y: "110vh",
    delay: 0.2,
    duration: 0.8,
    ease: "power4.out"
  })
  tl.to(
    ".overlay",
    {
      opacity: 1,
      duration: 0.3,
      ease: "power1.out"
    },
    0
  )
  tl.to(
    ".content.first",
    {
      scale: 0.95,
      duration: 0.3,
      ease: "power1.out"
    },
    0
  )
}

$(".nav-list a:not(.active)").on("click", function (e) {
  e.preventDefault()
  nextLink = this.href
  $.ajax({
    url: this.href,
    success: function (data) {
      let el = $(data).find(".content").addClass("sec")
      $(".wrapper").append(el)
    },
    complete: function () {
      pageTransition()
    }
  })
})

Vậy là đã xong, cùng xem thành quả nào.

ezgif com-video-to-gif (2)

Ok vậy là đã xong rồi. Mượt như sunsilk 😼😼😼.

# Kết thúc.

Vậy là chúng ta đã hoàn thành xong rồi.
Cảm ơn mọi người đã dành thời gian đọc.
Mọi người có thể tham khảo code ở đâydemo ở đây

Trở về trang chủ

👋 0 người đang online