Ở 2 phần trước, chúng ta đã đi cùng nhau thực hiện một ví dụ tạo hiệu ứng số tăng dần mà thường xuất hiện ở các trang Landing Page. Thành quả cuối cũng đã khá hoàn thiện, chúng ta tìm hiểu được thêm một hàm mới toanh – requestAnimationFrame()
trong Javascript, cũng như sử dụng phương pháp tính toán được giá trị số dựa theo thời gian,…
Hàm chuyển động của animation
Trước hết mình xin được đưa lại ví dụ CodePen – thành quả cuối cùng của chúng ta ở phần trước:
Nếu nhìn kỹ, hiệu ứng tăng của mấy con số trên trông có vẻ gì đó hơi… nhàm chán. Rút cuộc là chúng ta đang thiếu cái gì?
Theo mình, nó là bởi giá trị của mấy con số hiện đang được tăng đều với giá trị của thời gian. Nói cách khác, hiệu ứng của chúng ta đang chạy với tốc độ tuyến tính – không thay đổi theo thời gian.
Không có vật thể nào trong tự nhiên lại chuyển động với vận tốc đều với dạng đồ thị góc 45 độ như hình trên cả. Chả trách có gì đó sai sai.
Nếu bạn từng dùng CSS3 animation với transition, thì bán sẽ biết rằng có khá nhiều loại hàm chuyển động khác nhau để bạn lựa chọn như: linear, ease (chậm – nhanh – chậm), ease-in (khởi động chậm), ease-out (kết thúc chậm),…
Ở trang easings.net có sẵn rất nhiều hàm chuyển động mà mình có thể áp dụng cho hiệu ứng trong ví dụ của mình. Mình sẽ thử lấy hàm easeOutExpo để sử dụng. Nhớ rằng các hàm ease-out có khởi đầu nhanh, nhưng càng về cuối lại chuyển động chậm lại.
1 2 3 4 5 6 | <span class="token comment">// Lấy tại https://easings.net/#easeOutExpo</span> <span class="token keyword">function</span> <span class="token function">easeOutExpo</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> x <span class="token operator">===</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token punctuation">:</span> <span class="token number">1</span> <span class="token operator">-</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">10</span> <span class="token operator">*</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Hàm này nhận một tham số là giá trị x
chính là tỉ lệ thời gian (trong khoảng từ 0 đến 1), và sẽ trả về tỉ lệ của quãng đường trong khoảng từ 0 đến 1.
Giờ mình sẽ bổ sung thêm hàm trên vào hàm tính toán animation của chúng ta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <span class="token keyword">function</span> <span class="token function">easeOutExpo</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> x <span class="token operator">===</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token punctuation">:</span> <span class="token number">1</span> <span class="token operator">-</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">10</span> <span class="token operator">*</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">animateNumber</span><span class="token punctuation">(</span>finalNumber<span class="token punctuation">,</span> duration <span class="token operator">=</span> <span class="token number">5000</span><span class="token punctuation">,</span> startNumber <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> callback<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> startTime <span class="token operator">=</span> performance<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">function</span> <span class="token function">updateNumber</span><span class="token punctuation">(</span>currentTime<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> elapsedTime <span class="token operator">=</span> currentTime <span class="token operator">-</span> startTime <span class="token keyword">if</span> <span class="token punctuation">(</span>elapsedTime <span class="token operator">></span> duration<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">callback</span><span class="token punctuation">(</span>finalNumber<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> timeRate <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">*</span> elapsedTime<span class="token punctuation">)</span> <span class="token operator">/</span> duration <span class="token keyword">const</span> numberRate <span class="token operator">=</span> <span class="token function">easeOutExpo</span><span class="token punctuation">(</span>timeRate<span class="token punctuation">)</span> <span class="token keyword">const</span> currentNumber <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">round</span><span class="token punctuation">(</span>numberRate <span class="token operator">*</span> finalNumber<span class="token punctuation">)</span> <span class="token function">callback</span><span class="token punctuation">(</span>currentNumber<span class="token punctuation">)</span> <span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span>updateNumber<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span>updateNumber<span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Thay đổi so với ở bài trước của mình như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token coord">@@ -1,3 +1,7 @@</span> <span class="token inserted">+function easeOutExpo(x) {</span> <span class="token inserted">+ return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);</span> <span class="token inserted">+}</span> <span class="token inserted">+</span> function animateNumber(finalNumber, duration = 5000, startNumber = 0, callback) { const startTime = performance.now() function updateNumber(currentTime) { <span class="token coord">@@ -5,8 +9,9 @@</span> function animateNumber(finalNumber, duration = 5000, startNumber = 0, callback) if (elapsedTime > duration) { callback(finalNumber) } else { <span class="token deleted">- const rate = elapsedTime / duration</span> <span class="token deleted">- const currentNumber = Math.round(rate * finalNumber)</span> <span class="token inserted">+ const timeRate = (1.0 * elapsedTime) / duration</span> <span class="token inserted">+ const numberRate = easeOutExpo(timeRate)</span> <span class="token inserted">+ const currentNumber = Math.round(numberRate * finalNumber)</span> callback(currentNumber) requestAnimationFrame(updateNumber) } |
Thành quả là đây (nhấn nút 0,5x để xem toàn cảnh hơn). Giờ bạn đã có thể thấy càng về cuối chuyển động của các con số càng chậm lại, trông tự nhiên hơn rất rất nhiều.
Nâng cao hơn, bạn có thể tìm hiểu và áp dụng hàm đường cong bezier cho hiệu ứng, được hầu hết các hàm chuyển động trong CSS3 sử dụng. Có bài viết rất hay (bằng tiếng Việt) về đường cong bezier trong CSS3 animation ở đây mà bạn nên đọc thử.