Cách tạo hiệu ứng các nút như framework Material Design của google

Material Design, một bộ framework rất nổi tiếng của google về thành phần nút của nó. Nó sử dụng hiệu ứng gợn sóng để cung cấp cho người dùng phản hồi một cách đơn giản, thanh lịch. Hiệu ứng này hoạt động như thế nào? Các nút của Material Design không chỉ tạo ra hoạt ảnh gợn sóng gọn gàng mà hoạt ảnh còn thay đổi vị trí tùy thuộc vào vị trí mỗi nút được nhấp.

Hiệu ứng này hoạt động như thế nào? Các nút của Material Design không chỉ tạo ra hoạt ảnh gợn sóng gọn gàng mà hoạt ảnh còn thay đổi vị trí tùy thuộc vào vị trí mỗi nút được nhấp.

HTML

Mục tiêu của chúng tôi là tránh bất kỳ mã đánh dấu HTML không liên quan nào. Vì vậy, chúng tôi sẽ đi với mức tối thiểu:

<button>Find out more</button>

Tạo kiểu cho nút

Chúng tôi sẽ cần tạo kiểu động cho một số yếu tố của ripple bằng cách sử dụng JavaScript. Nhưng mọi thứ khác có thể được thực hiện trong CSS. Đối với các nút của chúng tôi, chỉ cần bao gồm hai thuộc tính.

button {
  position: relative;
  overflow: hidden;
}

Dùng position: relative cho phép chúng tôi tạo position: absolute trên phần tử gợn sóng của chúng tôi, mà chúng tôi cần kiểm soát vị trí của nó. Trong khi đó, overflow: hidden ngăn gợn sóng vượt quá các cạnh của nút. Mọi thứ khác là tùy chọn. Nhưng hiện tại, nút của chúng tôi trông hơi cũ. Đây là một điểm khởi đầu hiện đại hơn:

/* Roboto is Material's default font */
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

button {
  position: relative;
  overflow: hidden;
  transition: background 400ms;
  color: #fff;
  background-color: #6200ee;
  padding: 1rem 2rem;
  font-family: 'Roboto', sans-serif;
  font-size: 1.5rem;
  outline: 0;
  border: 0;
  border-radius: 0.25rem;
  box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

Tạo kiểu gợn sóng (ripples)

Sau đó, chúng tôi sẽ sử dụng JavaScript để đưa các gợn sóng vào HTML của mình dưới dạng các nhịp với lớp .ripple. Nhưng trước khi chuyển sang JavaScript, hãy xác định kiểu cho những gợn sóng đó trong CSS để nhúng:

span.ripple {
  position: absolute; /* The absolute position we mentioned earlier */
  border-radius: 50%;
  transform: scale(0);
  animation: ripple 600ms linear;
  background-color: rgba(255, 255, 255, 0.7);
}

Để làm cho các gợn sóng của chúng tôi có hình tròn, chúng tôi đã đặt bán kính đường viền thành 50%. Và để đảm bảo mỗi gợn sóng xuất hiện từ hư không, chúng tôi đã đặt tỷ lệ mặc định thành 0. Hiện tại, chúng tôi sẽ không thể nhìn thấy bất kỳ thứ gì vì chúng tôi chưa có giá trị cho trên cùng, bên trái, chiều rộng hoặc chiều cao đặc tính; chúng tôi sẽ sớm đưa các thuộc tính này vào JavaScript.

Đối với CSS của chúng tôi, điều cuối cùng chúng tôi cần thêm là trạng thái kết thúc cho hoạt ảnh:

@keyframes ripple {
  to {
    transform: scale(4);
    opacity: 0;
  }
}

Lưu ý rằng chúng tôi không xác định trạng thái bắt đầu với từ khóa from trong khung hình chính? Chúng ta có thể bỏ qua và CSS sẽ xây dựng các giá trị còn thiếu dựa trên những giá trị áp dụng cho phần tử động. Điều này xảy ra nếu các giá trị có liên quan được nêu rõ ràng – như trong biến đổi: scale (0) – hoặc nếu chúng là giá trị mặc định, như opacity: 1.

Bây giờ bắt đầu thêm JavaScript

Cuối cùng, chúng ta cần JavaScript để đặt động vị trí và kích thước của gợn sóng. Kích thước phải dựa trên kích thước của nút, trong khi vị trí phải dựa trên cả vị trí của nút và của con trỏ.

Chúng ta sẽ bắt đầu với một hàm trống lấy một sự kiện nhấp chuột làm đối số của nó:

function createRipple(event) {
  //
}

Chúng tôi sẽ truy cập vào nút của mình bằng cách tìm Mục tiêu hiện tại của sự kiện.

const button = event.currentTarget;

Tiếp theo, chúng tôi sẽ khởi tạo phần tử nhịp của mình và tính toán đường kính và bán kính của nó dựa trên chiều rộng và chiều cao của nút.

const circle = document.createElement("span");
const diameter = Math.max(button.clientWidth, button.clientHeight);
const radius = diameter / 2;

Bây giờ có thể xác định các thuộc tính còn lại chúng tôi cần cho gợn sóng: left, top, widthheight.

circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (button.offsetLeft + radius)}px`;
circle.style.top = `${event.clientY - (button.offsetTop + radius)}px`;
circle.classList.add("ripple"); 

Trước khi thêm phần tử span của chúng tôi vào DOM, bạn nên kiểm tra bất kỳ gợn sóng hiện có nào có thể còn sót lại từ các nhấp chuột trước đó và xóa chúng trước khi thực hiện nhấp chuột tiếp theo.

const ripple = button.getElementsByClassName("ripple")[0];

if (ripple) {
  ripple.remove();
}

Bước cuối cùng, nối span dưới dạng phần tử con vào phần tử nút để nó được đưa vào bên trong nút.

button.appendChild(circle);

Với chức năng của chúng tôi đã hoàn thành, tất cả những gì còn lại là gọi nó. Điều này có thể được thực hiện theo một số cách. Nếu chúng ta muốn thêm gợn sóng vào mọi nút trên trang của mình, chúng ta có thể sử dụng một cái gì đó như sau:

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  button.addEventListener("click", createRipple);
}

Bây giờ chúng ta có một hiệu ứng gợn sóng đang hoạt động!

Mở rộng thêm

Điều gì sẽ xảy ra nếu chúng ta muốn mở rộng và kết hợp hiệu ứng này với các thay đổi khác đối với vị trí hoặc kích thước nút của chúng ta? Xét cho cùng, khả năng tùy chỉnh là một trong những lợi thế chính mà chúng tôi có được bằng cách chọn tự tạo lại hiệu ứng. Để kiểm tra mức độ dễ dàng khi mở rộng chức năng của chúng tôi, tôi đã quyết định thêm hiệu ứng “nam châm”, làm cho nút của chúng tôi di chuyển về phía con trỏ khi con trỏ ở trong một khu vực nhất định.

Chúng ta cần dựa vào một số biến tương tự được xác định trong hàm ripple. Thay vì lặp lại mã một cách không cần thiết, chúng ta nên lưu trữ chúng ở nơi nào đó mà chúng có thể truy cập được bằng cả hai phương pháp. Nhưng chúng ta cũng nên giữ các biến được chia sẻ có phạm vi đến từng nút riêng lẻ. Một cách để đạt được điều này là sử dụng các lớp, như trong ví dụ dưới đây:

Vì hiệu ứng nam châm cần theo dõi con trỏ mỗi khi nó di chuyển, chúng ta không cần phải tính toán vị trí con trỏ để tạo ra gợn sóng nữa. Thay vào đó, chúng ta có thể dựa vào cursorXcursorY.

Hai biến mới quan trọng là magneticPullXmagneticPullY. Chúng kiểm soát mức độ mạnh mẽ của phương pháp nam châm kéo nút sau con trỏ. Vì vậy, khi chúng ta xác định tâm của gợn sóng, chúng ta cần điều chỉnh cho cả vị trí của nút mới (x và y) và lực kéo từ tính.

const offsetLeft = this.left + this.x * this.magneticPullX;
const offsetTop = this.top + this.y * this.magneticPullY;

Để áp dụng các hiệu ứng kết hợp này cho tất cả các nút của chúng ta, chúng ta cần khởi tạo một phiên bản mới của lớp cho mỗi nút:

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  new Button(button);
}

Các kỹ thuật khác

Tất nhiên, đây chỉ là một cách để đạt được hiệu ứng gợn sóng. Trên CodePen, có rất nhiều ví dụ hiển thị các cách triển khai khác nhau. Dưới đây là một số mục yêu thích của mình. Chỉ CSS

Nếu người dùng đã tắt JavaScript, hiệu ứng gợn sóng sẽ không hoạt động mà không có bất kỳ dự phòng nào. Nhưng có thể đạt được hiệu ứng ban đầu chỉ với CSS, bằng cách sử dụng: lớp giả hoạt động để phản hồi các lần nhấp. Hạn chế chính là gợn sóng chỉ có thể xuất hiện từ một vị trí – thường là trung tâm của nút – thay vì phản hồi vị trí của các lần nhấp của chúng tôi. Ví dụ này của Ben Szabo đặc biệt ngắn gọn:

Pre-ES6 JavaScript

Bản demo của Leandro Parice tương tự như cách triển khai của chúng tôi nhưng nó tương thích với các phiên bản JavaScript trước đó:

jQuery

Ví dụ này sử dụng jQuery để đạt được hiệu ứng gợn sóng. Nếu bạn đã có jQuery làm phụ thuộc, nó có thể giúp bạn tiết kiệm một vài dòng mã.

React

Một ví dụ cuối cùng. Mặc dù có thể sử dụng các tính năng của React như trạng thái và giới thiệu để giúp tạo hiệu ứng gợn sóng, nhưng những tính năng này không hoàn toàn cần thiết. Cả vị trí và kích thước của gợn sóng đều cần được tính toán cho mỗi lần nhấp chuột, vì vậy không có lợi ích gì khi giữ thông tin đó ở trạng thái. Ngoài ra, chúng tôi có thể truy cập phần tử nút của mình từ sự kiện nhấp chuột, vì vậy chúng tôi cũng không cần giới thiệu.

Ví dụ về React này sử dụng createRipple chức năng giống với chức năng của lần triển khai đầu tiên của bài viết này. Sự khác biệt chính là – với tư cách là một phương thức của thành phần Nút – chức năng của chúng tôi được xác định phạm vi cho thành phần đó. Ngoài ra, trình xử lý sự kiện onClick hiện là một phần của JSX.

DigitalOcean hợp tác với Wuuget.com! Ưu đãi chào mừng đặc biệt cho đăng ký và sử dụng Cloud VPS: nhận 100 đô la tín dụng miễn phí.

Đã đăng trong Code Snippets, CSS.