先看效果
- Card 1
- Card 2
- Card 3
- Card 4
- Card 5
- Card 6
- Card 7
- Card 8
- Card 9
- Card 10
那么怎么实现呢,首先是 HTML 结构:
1 2 3 4 5 6 7 8 9 10 11 12
| <ul> <li>Card 1</li> <li>Card 2</li> <li>Card 3</li> <li>Card 4</li> <li>Card 5</li> <li>Card 6</li> <li>Card 7</li> <li>Card 8</li> <li>Card 9</li> <li>Card 10</li> </ul>
|
只要是个列表就行,随便什么div都可以。
样式也随意,只需要注意 ul 的 position 不是能初始值,可以给一个 relative,否则会影响计算子元素的 offsetTop。
定义动画函数,并监听 ul 的滚动事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function fadeout(el) { const len = el.childElementCount; let fadeOutAnimating = false; function fadeoutEffect() { fadeOutAnimating = true; fadeOutAnimating = false; } function animate() { if (!fadeOutAnimating) { requestAnimationFrame(fadeoutEffect); } } el.addEventListener('scroll', animate); }
|
动画逻辑就是当滚动进行时,当子元素超出父元素可视范围时,将滚动距离与子元素的高度做转换,使用 requestAnimationFrame 设置子元素大小及透明度样式,那么随着滚动距离越大,子元素就越小,不透明度越来越低
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function fadeoutEffect() { fadeOutAnimating = true; for (let i = 0; i < len; i++) { if (el.scrollTop > el.children[i].offsetTop) { const scrolling = el.scrollTop - el.children[i].offsetTop; const scaling = Math.min(1, Math.max(0, (el.children[i].offsetHeight - scrolling * 0.2) / el.children[i].offsetHeight)); if (!el.children[i].classList.contains('fadeOutScalingTop')) { el.children[i].classList.add('fadeOutScalingTop'); } const opacity = scaling * 100 < 96 ? scaling ** 8 : 1; if (opacity > 0) { el.children[i].style.opacity = opacity; el.children[i].style.transform = `translate3d(0, ${scrolling}px, 0) scale(${scaling})`; } } else if (el.children[i].classList.contains('fadeOutScalingTop')) { el.children[i].classList.remove('fadeOutScalingTop'); el.children[i].style.transform = 'none'; el.children[i].style.opacity = 1; } } fadeOutAnimating = false; }
|
动画效果中给子元素添加了 className fadeOutScalingTop, 主要是给子元素设置了 transform-origin 属性:
1 2 3 4 5 6 7
| .fadeOutScalingTop { position: relative; position: -webkit-sticky; position: sticky; -webkit-transform-origin: center top; transform-origin: center top; }
|
为了性能好一些可以给子元素设置上 will-change: transform 属性。
现在动效逻辑就完成了,调用即可:
1
| fadeout(document.querySelector('ul')[0]);
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| /** * Copyright ThomasChan. * This source code is licensed under the MIT license. */
<style> #demo { position: relative; width: 200px; height: 400px; overflow: overlay; border: 1px solid #dadada; border-radius: 2px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); padding: 0px 15px; } #demo li { display: flex; justify-content: center; align-items: center; list-style: none; margin: 15px auto; height: 100px; border: 1px solid #dadada; border-radius: 2px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); will-change: transform; background-color: white; } .fadeOutScalingTop { position: relative; position: -webkit-sticky; position: sticky; -webkit-transform-origin: center top; transform-origin: center top; } </style> <ul id="demo"> <li>Card 1</li> <li>Card 2</li> <li>Card 3</li> <li>Card 4</li> <li>Card 5</li> <li>Card 6</li> <li>Card 7</li> <li>Card 8</li> <li>Card 9</li> <li>Card 10</li> </ul> <script> function fadeout(el) { const len = el.childElementCount; let fadeOutAnimating = false; const childSize = 100; function fadeoutEffect() { fadeOutAnimating = true; for (let i = 0; i < len; i++) { if (el.scrollTop > el.children[i].offsetTop) { const scrolling = el.scrollTop - el.children[i].offsetTop; const scaling = Math.min(1, Math.max(0, (childSize - scrolling * 0.2) / childSize)); const opacity = scaling * 100 < 96 ? scaling ** 8 : 1; if (opacity > 0) { if (!el.children[i].classList.contains('fadeOutScalingTop')) { el.children[i].classList.add('fadeOutScalingTop'); } el.children[i].style.opacity = opacity; el.children[i].style.transform = `translate3d(0, ${scrolling}px, 0) scale(${scaling})`; } } else if (el.children[i].classList.contains('fadeOutScalingTop')) { el.children[i].classList.remove('fadeOutScalingTop'); el.children[i].style.transform = 'none'; el.children[i].style.opacity = 1; } } fadeOutAnimating = false; } function animate() { if (!fadeOutAnimating) { requestAnimationFrame(fadeoutEffect); } } el.addEventListener('scroll', animate); } window.onload = function() { fadeout(document.querySelector('#demo')); } </script>
|