Cách tạo trình tải chỉ CSS bằng một phần tử

Nếu bạn có một trang web, sẽ rất hữu ích nếu bạn có một trình tải để người dùng có thể biết điều gì đó đang xảy ra sau khi họ nhấp vào một liên kết hoặc nút.

Bạn có thể sử dụng thành phần bộ nạp này ở nhiều nơi và nó phải càng đơn giản càng tốt.

Trong bài đăng này, chúng ta sẽ thấy cách tạo hai loại bộ nạp chỉ có một <div> và một vài dòng mã CSS. Không chỉ điều này mà chúng tôi sẽ làm cho chúng có thể tùy chỉnh để bạn có thể dễ dàng tạo các biến thể khác nhau từ cùng một mã.

Đây là những gì chúng tôi sẽ xây dựng:

Spinner chỉ CSS và Trình tải tiến trình
Spinner chỉ CSS và Trình tải tiến trình

Cách tạo Bộ tải Spinner

Dưới đây là bản demo về những gì chúng tôi đang xây dựng:

Bấm để xem mã đầy đủ
 <div class="loader"></div>
 <div class="loader" style="--b: 15px;--c: blue;width: 120px;--n: 8"></div>
 <div class="loader" style="--b: 5px;--c: green;width: 80px;--n: 6;--g: 20deg"></div>
 <div class="loader" style="--b: 20px;--c: #000;width: 80px;--n: 15;--g: 7deg"></div> 
 .loader {
   --b: 10px;  /* border thickness */
   --n: 10;    /* number of dashes*/
   --g: 10deg; /* gap between dashes*/
   --c: red;   /* the color */

   width: 100px; /* size */
   aspect-ratio: 1;
   border-radius: 50%;
   padding: 1px;
   background: conic-gradient(#0000,var(--c)) content-box;
   -webkit-mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
           mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
   -webkit-mask-composite: destination-in;
           mask-composite: intersect;
   animation: load 1s infinite steps(var(--n));
 }
 @keyframes load {to{transform: rotate(1turn)}}

Chúng tôi có 4 bộ nạp khác nhau sử dụng cùng một mã. Chỉ cần thay đổi một vài biến, chúng ta có thể tạo một trình tải mới mà không cần chạm vào mã CSS.

Các biến được định nghĩa như sau:

  • --b xác định độ dày của đường viền.
  • --n xác định số lượng dấu gạch ngang.
  • --g xác định khoảng cách giữa các dấu gạch ngang. Vì chúng ta đang xử lý một phần tử hình tròn, phần tử này là một giá trị góc.
  • --c xác định màu sắc.

Đây là một minh họa để xem các biến khác nhau.

Các biến CSS của trình tải Spinner
Các biến CSS của trình tải Spinner

Hãy giải quyết mã CSS. Chúng tôi sẽ sử dụng một hình khác để minh họa cấu tạo từng bước của bộ nạp.

Hình minh họa từng bước của Spinner Loader
Hình minh họa từng bước của Spinner Loader

Đầu tiên chúng ta bắt đầu bằng cách tạo một vòng kết nối như thế này:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
}

Không có gì phức tạp cho đến nay. Lưu ý việc sử dụng aspect-ratio cho phép chúng tôi chỉ sửa đổi một giá trị ( width) để kiểm soát kích thước.

Sau đó, chúng tôi thêm một màu chuyển sắc conic từ trong suốt sang màu đã xác định (biến --c):

.loader {
  width:100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
}

Trong bước này, chúng tôi giới thiệu mask thuộc tính ẩn một số phần của vòng tròn theo cách lặp đi lặp lại. Điều này sẽ phụ thuộc vào --n--d biến. Nếu bạn quan sát kỹ hình vẽ, chúng ta sẽ nhận thấy mô hình sau:

visible part
invisible part
visible part
invisible part
etc

Để làm điều này, chúng tôi sử dụng repeating-conic-gradient(#000 0 X, #0000 0 Y). Từ 0 đến X chúng tôi có một màu mờ đục (phần nhìn thấy được) và từ X đến Y chúng ta có một trong suốt (phần vô hình).

Chúng tôi giới thiệu các biến của chúng tôi:

  • Chúng ta cần một khoảng cách bằng g giữa mỗi phần có thể nhìn thấy nên công thức giữa XY sẽ là X = Y - g.
  • Chúng tôi cần n phần hiển thị nên công thức của Y nên là Y = 360deg/n. Một vòng tròn đầy đủ là 360deg vì vậy chúng tôi chỉ cần chia nó cho n

Mã của chúng tôi cho đến nay là:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
  mask: repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n))
}

Bước tiếp theo này là bước khó nhất, bởi vì chúng ta cần áp dụng một mặt nạ khác để tạo một loại lỗ để có được hình dạng cuối cùng. Để làm điều này, chúng tôi sẽ sử dụng một cách hợp lý radial-gradient() với biến của chúng tôi b:

radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0)

Một vòng tròn đầy đủ mà từ đó chúng tôi loại bỏ độ dày bằng b.

Chúng tôi thêm cái này vào mặt nạ trước:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
  mask: 
   radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0),
   repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n))
}

Chúng tôi có hai lớp mặt nạ, nhưng kết quả không như chúng tôi muốn. Chúng tôi nhận được những điều sau đây:

image 52

Nó có thể trông kỳ lạ nhưng nó hợp lý. Phần có thể nhìn thấy “cuối cùng” không là gì ngoài tổng của mỗi phần có thể nhìn thấy của mỗi lớp mặt nạ. Chúng tôi có thể thay đổi hành vi này bằng cách sử dụng mask-composite. Tôi sẽ cần cả một bài báo để giải thích thuộc tính này vì vậy tôi sẽ chỉ đơn giản là đưa ra giá trị.

Trong trường hợp của chúng tôi, chúng tôi cần xem xét intersect (và destination-out cho thuộc tính tiền tố). Mã của chúng tôi sẽ trở thành:

.loader {
  width: 100px; /* size */
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(#0000,var(--c));
  mask: 
    radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0),
    repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n));
  -webkit-mask-composite: destination-in;
          mask-composite: intersect;
}

Chúng tôi đã hoàn thành với hình dạng! Chúng tôi chỉ thiếu hình ảnh động. Sau đó là một vòng quay vô hạn.

Điều duy nhất cần lưu ý là tôi đang sử dụng steps hoạt ảnh để tạo ảo giác về các dấu gạch ngang cố định và các màu chuyển động.

Dưới đây là hình ảnh minh họa để thấy sự khác biệt

steps final
Hoạt ảnh tuyến tính so với hoạt ảnh từng bước

Hình đầu tiên là chuyển động quay tuyến tính và liên tục của hình dạng (không phải những gì chúng ta muốn) và hình thứ hai là hoạt ảnh rời rạc (hình mà chúng ta muốn).

Đây là mã đầy đủ bao gồm cả hoạt ảnh:

Bấm để xem mã đầy đủ
 <div class="loader"></div>
 <div class="loader" style="--b: 15px;--c: blue;width: 120px;--n: 8"></div>
 <div class="loader" style="--b: 5px;--c: green;width: 80px;--n: 6;--g: 20deg"></div>
 <div class="loader" style="--b: 20px;--c: #000;width: 80px;--n: 15;--g: 7deg"></div> 
 .loader {
   --b: 10px;  /* border thickness */
   --n: 10;    /* number of dashes*/
   --g: 10deg; /* gap between dashes*/
   --c: red;   /* the color */

   width: 100px; /* size */
   aspect-ratio: 1;
   border-radius: 50%;
   padding: 1px;
   background: conic-gradient(#0000,var(--c)) content-box;
   -webkit-mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
           mask:
     repeating-conic-gradient(#0000 0deg,
        #000 1deg calc(360deg/var(--n) - var(--g) - 1deg),
        #0000     calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))),
     radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b)));
   -webkit-mask-composite: destination-in;
           mask-composite: intersect;
   animation: load 1s infinite steps(var(--n));
 }
 @keyframes load {to{transform: rotate(1turn)}}

Bạn sẽ nhận thấy một số khác biệt với mã tôi đã sử dụng trong phần giải thích:

  • Tôi đang thêm padding: 1px và đặt nền thành content-box
  • +/1deg giữa các màu của repeating-conic-gradient()
  • Có một vài phần trăm sự khác biệt giữa màu sắc bên trong radial-gradient()

Đó là một số chỉnh sửa để tránh rối mắt. Gradients được biết là tạo ra kết quả “lạ” trong một số trường hợp vì vậy chúng tôi phải điều chỉnh một số giá trị theo cách thủ công để tránh chúng.

Cách tạo bộ tải tiến trình

Giống như trình tải trước đó, hãy bắt đầu với tổng quan:

Bấm để xem mã đầy đủ
 <div class="loader"></div>
 <div class="loader" style="--s:10px;--n:10;color:red"></div>
 <div class="loader" style="--g:0px;color:darkblue"></div>
 <div class="loader" style="--s:25px;--g:8px;border-radius:50px;color:green"></div>
 .loader {
   --n:5;    /* control the number of stripes */
   --s:30px; /* control the width of stripes */
   --g:5px;  /* control the gap between stripes */

   width:calc(var(--n)*(var(--s) + var(--g)) - var(--g));
   height:30px;
   padding:var(--g);
   margin:5px auto;
   border:1px solid;
   background:
     repeating-linear-gradient(90deg,
       currentColor  0 var(--s),
       #0000 0 calc(var(--s) + var(--g))
     ) left / calc((var(--n) + 1)*(var(--s) + var(--g))) 100% 
     no-repeat content-box;
   animation: load 1.5s steps(calc(var(--n) + 1)) infinite;
 }
 @keyframes load {
   0% {background-size: 0% 100%}
 }

Chúng ta có cấu hình tương tự như bộ nạp trước đó. Các biến CSS điều khiển trình tải:

  • --n xác định số lượng dấu gạch ngang / sọc.
  • --s xác định chiều rộng của mỗi sọc.
  • --g xác định khoảng cách giữa các sọc.
Minh họa về các biến CSS
Minh họa về các biến CSS

Từ hình trên, chúng ta có thể thấy rằng độ rộng của phần tử sẽ phụ thuộc vào 3 biến. CSS sẽ như sau:

.loader {
  width: calc(var(--n)*(var(--s) + var(--g)) - var(--g));
  height: 30px; /* use any value you want here */
  padding: var(--g);
  border: 1px solid;
}

Chúng tôi sử dụng padding để thiết lập khoảng cách ở mỗi bên. Khi đó chiều rộng sẽ bằng số sọc nhân với chiều rộng và khoảng cách của chúng. Chúng tôi xóa một khoảng cách bởi vì N sọc chúng tôi có N-1 những khoảng trống.

Để tạo các sọc, chúng ta sẽ sử dụng gradient bên dưới.

repeating-linear-gradient(90deg,
  currentColor 0 var(--s),
  #0000        0 calc(var(--s) + var(--g))
 )

Từ 0 đến s là màu xác định và từ s đến s + g một màu trong suốt (khoảng trống).

tôi đang dùng currentColor đó là giá trị của color bất động sản. Lưu ý rằng tôi không xác định bất kỳ màu nào bên trong border vì vậy nó cũng sẽ sử dụng giá trị của color. Nếu chúng ta muốn thay đổi màu của bộ nạp, chúng ta chỉ cần đặt color bất động sản.

Mã của chúng tôi cho đến nay:

.loader {
  width: calc(var(--n)*(var(--s) + var(--g)) - var(--g));
  height: 30px;
  padding: var(--g);
  border: 1px solid;
  background:
    repeating-linear-gradient(90deg,
      currentColor  0 var(--s),
      #0000 0 calc(var(--s) + var(--g))
    ) left / 100% 100% content-box no-repeat;
}

tôi đang dùng content-box để đảm bảo gradient không che vùng đệm. Sau đó, tôi xác định kích thước bằng 100% 100% và một vị trí bên trái.

Đã đến lúc hoạt hình. Đối với trình tải này, chúng tôi sẽ tạo hoạt ảnh background-size từ 0% 100% đến 100% 100% có nghĩa là chiều rộng của gradient của chúng tôi từ 0% đến 100%

Giống như trình tải trước, chúng tôi sẽ dựa vào steps() để có hoạt ảnh rời rạc thay vì liên tục.

steps 2 final
Hoạt ảnh tuyến tính so với hoạt ảnh từng bước

Cái thứ hai là thứ chúng ta muốn tạo và chúng ta có thể đạt được nó bằng cách thêm đoạn mã sau:

.loader {
  animation: load 1.5s steps(var(--n)) infinite;
}
@keyframes load {
  0% {background-size: 0% 100%}
}

Nếu bạn nhìn kỹ vào hình cuối cùng, bạn sẽ nhận thấy rằng hoạt ảnh chưa hoàn chỉnh. Chúng tôi thiếu một sọc ở cuối, ngay cả khi chúng tôi đã sử dụng N. Đây không phải là một lỗi nhưng làm thế nào steps() được cho là hoạt động.

Để khắc phục điều này, chúng ta cần thêm một bước bổ sung. Chúng tôi tăng background-size gradient của chúng tôi để chứa N+1 sọc và sử dụng steps(N+1). Điều này sẽ đưa chúng ta đến mã cuối cùng:

.loader {
  width: calc(var(--n)*(var(--s) + var(--g)) - var(--g));
  height: 30px;
  padding: var(--g);
  margin: 5px auto;
  border: 1px solid;
  background:
    repeating-linear-gradient(90deg,
      currentColor  0 var(--s),
      #0000 0 calc(var(--s) + var(--g))
    ) left / calc((var(--n) + 1)*(var(--s) + var(--g))) 100% 
    content-box no-repeat;
  animation: load 1.5s steps(calc(var(--n) + 1)) infinite;
}
@keyframes load {
  0% {background-size: 0% 100%}
}

Lưu ý rằng chiều rộng của gradient bằng N+1 nhân với chiều rộng của một sọc và một khoảng trống (thay vì 100% )

Sự kết luận

Tôi hy vọng bạn thích hướng dẫn này. Nếu bạn quan tâm đến, Tôi đã tạo hơn 500 trình tải div đơn chỉ sử dụng CSS. Tôi cũng đã viết một cái khác hướng dẫn giải thích cách tạo bộ tải Dots chỉ sử dụng các thuộc tính nền.

Tìm các liên kết hữu ích bên dưới để biết thêm chi tiết về một số thuộc tính tôi đã sử dụng mà tôi không giải thích cặn kẽ do tính phức tạp của chúng:

Cảm ơn bạn đã đọc!

Để biết thêm các mẹo CSS, hãy theo dõi tôi trên Twitter.

Thanks for Reading

Enjoyed this post? Share it with your networks.

Get more stuff

Subscribe to our mailing list and get interesting stuff and updates to your email inbox.

Thank you for subscribing.

Something went wrong.

Leave a Feedback!