使用CSS+JS帮你实现苹果cover flow效果

简介:

 
 
  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <title>coverflow-demo</title> 
  6.     <style> 
  7.         div.innerWrapper{ 
  8.             perspective: 300px; 
  9.             width: 600px; 
  10.             height: 300px; 
  11.             margin: 100px auto; 
  12.             display: flex;  
  13.             align-items:flex-start; 
  14.             background-color: #000;  
  15.             overflow: hidden;  
  16.             padding-top: 5%; 
  17.         } 
  18.         div.cover{ 
  19.             height: 50%;  
  20.             flex-grow:1; 
  21.             transition: all .5s ease; 
  22.             background-size: 100% 100%;  
  23.             background-repeat:no-repeat; 
  24.             margin: 0;  
  25.             -webkit-box-reflect:below 5% linear-gradient(transparent, white); 
  26.             border: 1px solid #fff; 
  27.  
  28.         } 
  29.         div.cover:nth-child(1){ 
  30.             background-image: url('covers/computergraphics-album-covers-2014-15.jpg'); 
  31.         } 
  32.         div.cover:nth-child(2){ 
  33.             background-image: url('covers/Funkadelic-Maggot-Brain-album-covers-billboard-1000x1000.jpg'); 
  34.         } 
  35.         div.cover:nth-child(3){ 
  36.             background-image: url('covers/Green-Day-American-Idiot-album-covers-billboard-1000x1000.jpg'); 
  37.         } 
  38.         div.cover:nth-child(4){ 
  39.             background-image: url('covers/insurgency-digital-album-cover-design.jpg'); 
  40.         } 
  41.         div.cover:nth-child(5){ 
  42.             background-image: url('covers/Pink-Floyd-Dark-Side-of-the-Moon-album-covers-billboard-1000x1000.jpg'); 
  43.         } 
  44.         div.cover:nth-child(6){ 
  45.             background-image: url('covers/sonic-quiver-time-and-space1-1000x1000.jpg'); 
  46.         } 
  47.         div.cover:nth-child(7){ 
  48.             background-image: url('covers/tumblr_inline_nydppi1Mp91t7tdyh_500.jpg'); 
  49.         } 
  50.         button[required='required']{ 
  51.             background-color: #000;  
  52.         } 
  53.     </style> 
  54. </head> 
  55. <body> 
  56.     <div class='container'
  57.         <div class="innerWrapper"
  58.             <div></div> 
  59.             <div></div> 
  60.             <div></div> 
  61.             <div></div> 
  62.             <div></div> 
  63.             <div></div> 
  64.             <div></div> 
  65.         </div> 
  66.     </div> 
  67.     <button required='required'>222</button> 
  68.     <script> 
  69.     ;(function(parent){ 
  70.         var cards = parent.querySelectorAll('div'), coverCount = cards.length, middleIndex = (coverCount-1)/2, middleCover = cards[middleIndex], parentWidth = middleCover.parentNode.clientWidth, currentIndex = middleIndex;  
  71.         var maxRotate = 42, stepper = maxRotate/middleIndex, maxZIndex = middleIndex + 1;  
  72.         var rotateReg = /rotateY\((\-?\d{1,3}\.?\d*)deg\)/, translateReg = /translateX\((\-?\d{1,3}\.?\d*)px\)/;  
  73.         // debugger;  
  74.         for(var i = 0; i<coverCount; i++){ 
  75.             var elem = cards[i];  
  76.             elem.classList.add('cover');  
  77.             elem.style.transform = 'translateX(0px) rotateY(' + (maxRotate-(i*stepper).toFixed(0)) + 'deg)';  
  78.             elem.style.flexGrow = 1;  
  79.             if(i<middleIndex){ 
  80.                 elem.style.zIndex = i+1;  
  81.             }else if(i == middleIndex){ 
  82.                 elem.style.zIndex = i+1;  
  83.                 elem.style.flexGrow = 2;  
  84.             }else
  85.                 elem.style.zIndex = coverCount - i;  
  86.             } 
  87.         } 
  88.         function move(direction){ 
  89.             if(currentIndex==(direction=='right'?0:coverCount-1))return;  
  90.             direction=='right'?currentIndex--:currentIndex++;  
  91.             maxZIndex++;  
  92.             [].forEach.call(cards, function(element, index) { 
  93.                 var previousRotate = parseInt(element.style.transform.match(rotateReg)[1]);  
  94.                 var previousTranslate = parseInt(element.style.transform.match(translateReg)[1]); 
  95.                 // translateX + 80 px one right button is clicked 
  96.                 var currentRotate, currentTranslate;  
  97.                 if(direction=='right'){ 
  98.                     currentRotate = previousRotate-stepper;  
  99.                     currentTranslate = previousTranslate+(parentWidth/(coverCount+1));  
  100.                 }else
  101.                     currentRotate = previousRotate+stepper;  
  102.                     currentTranslate = previousTranslate-(parentWidth/(coverCount+1));  
  103.                 } 
  104.                 element.style.transform = 'translateX(' + currentTranslate + 'px) rotateY('+ currentRotate +'deg)' 
  105.                 // element.style.zIndex =  
  106.                 if(index == currentIndex){ 
  107.                     element.style.flexGrow = 2;  
  108.                     element.style.zIndex = maxZIndex;   
  109.                 }else
  110.                     element.style.flexGrow = 1;  
  111.                 } 
  112.             }); 
  113.         } 
  114.         document.addEventListener('keyup'function(e){ 
  115.             if(e.which == 37){ 
  116.                 move('right');  
  117.             }else if(e.which == 39){ 
  118.                 move('left');  
  119.             } 
  120.         }) 
  121.     })(document.querySelector('.innerWrapper'));  
  122.     </script> 
  123. </body> 
  124. </html> 

稍微解释下这里用到的几个知识点:

1. flex-box.

什么是flex-box捏, 它是为了适应当前设备屏幕大小不一而提出的一种display方法. 当一个父元素的显示被设定为display:flex时, 它内部的子元素们会被平均分配占满父元素的空间, 并且当父元素的尺寸变化时, 子元素的尺寸也会相应变化! 是不是很神奇呢? 不仅如此, 你还可以任意分配子元素们的排列顺序, 如果觉得某个子元素需要突出显示, 就可以给这个子元素以特殊身份, 让它相比其他子元素大一些, 或者小一些! 由于其的自适应特性, flex是移动开发的一把利器, 我们先来看看一个小应用:

设计一个对话框函数, 当传入一个回调函数时, 只显示一个'确定'按钮, 占100%宽度; 当传入两个回调函数(确定和取消)时, 分别显示'确定'和'取消'按钮, 各占50%宽度, 样式分别如下:

怎么实现呢? 我们当然可以用JS来做, 但是(凡事就怕一个但是哈哈)! 我们作为有追求的前端, 战斗在CSS探索的第一线, 现在有了如此好用的flex属性, 为毛不立马用起来呢? 说走咱就走, 按钮容器和按钮本身的CSS如下:


关键是按钮的width:100%属性. 有了它, 当容器里只有一个按钮时, 它的宽度会拓展为容器的100%宽度; 而当容器里有两个按钮时, 按钮的宽度都为100%, 怎么办呢? 由于两个按钮势均力敌, 它们只好平分秋色, 各占50%的空间了!

有的同学要问了: 要是我不想按钮把空间占满怎么办呢? 这时候, 可以设定按钮的宽度各为45%, 然后在父元素上设置justify-content:center, 意思是两个子元素只占了90%的横向空间, 那怎么分配剩下的10%空间呢? 那就两边各分配5%吧! 除此之外, 该属性的其它值, 可以让子元素左右对齐, 更多flexbox的神奇应用, 请参考这篇文章~

A Complete Guide to Flexbox

回到我们的例子. 在卡片们没有被应用transform属性之前, 它们看起来是这个样子的:

七个元素平均分布, 占据了父元素的全部横向空间. 其中中间的元素应用了flex-grow:2的属性, 使得它比其他元素高人一等, 面积是其他元素的两倍~~

2. transform

其实有了上一幅图, 初始页面的雏形就已经差不多了~现在只需要给父元素设置视角(关于视角, 3D变换等内容, 请见我的这一篇文章). 为了得到比较明显3D效果, 设置了父元素的perspective为较小的值300px, 就相当于从距离3D变换平面300px的距离看. perspective的值越大, 相当于从越远的距离看, 3D效果越不明显, 平面化效果越强烈~

设置好了视角, 接下来该给元素们设置3D效果了. 第一步很简单: 假设有7个元素, 沿Y轴最大旋转角度为42度, 则0,1,2号元素分别旋转42, 28, 14度, 3号元素旋转0度同时变大2倍,4,5,6号元素分别旋转-14, -28, -42度. 用一个简单的for循环就可以完成这项任务, 代码如下:


 
 
  1. for(var i = 0; i<coverCount; i++){ 
  2.             var elem = cards[i];  
  3.             elem.classList.add('cover');  
  4.             // 设置元素的translateX为0px, 旋转角度为最大旋转角度-目录值*步进值 
  5.             elem.style.transform = 'translateX(0px) rotateY(' + (maxRotate-(i*stepper).toFixed(0)) + 'deg)';  
  6.             elem.style.flexGrow = 1;  
  7.             // 设置元素的z-index以区分前后顺序, 并将中间元素设置大一些 
  8.             if(i<middleIndex){ 
  9.                 elem.style.zIndex = i+1;  
  10.             }else if(i == middleIndex){ 
  11.                 elem.style.zIndex = i+1;  
  12.                 elem.style.flexGrow = 2;  
  13.             }else
  14.                 elem.style.zIndex = coverCount - i;  
  15.             } 
  16.         }  

初始化完成后效果图如下:

 

此时每个卡片的translateX为0, 这个值要预先写好, 才能通过改变该值来实现卡片的左右移动效果; rotateY的值分别为42, 28, 14, 0, -14, -28, 42度; flex-grow(相对于其它子元素的大小)分别为1, 1, 1, 2, 1, 1, 1

3.-webkit-box-reflect

那么漂亮的倒影是怎么实现的呢? 哈哈其实一行CSS就能搞定, 那就是强大的-webkit-box-reflect, 值为below 5% linear-gradient(transparent, white). 相信聪明的小伙伴看到这里已经明白了大概了, 为了避免误解, 稍稍再解释一下~below是倒影在盒子下方, 5%表示offset, 和盒子的距离是盒子宽度的5%, linear-gradient(transparent, white)指的是倒影的颜色, 从透明到完全不透明. 渐变语法的颜色在这里起作用的只有透明度, 白色的颜色是不会显出来的~到这里, 我们用的大部分都是CSS, 效果图如下:

然而静态的展示是不够的, 我们的目标是! 要让它动起来! 来回左右动! 到这里CSS已经无能为力, 改JS闪亮登场的时候了!~

4.JS控制

控制左右移动的函数如下, 接受一个参数left或者right表示要移动的方向~


 
 
  1. // 定义提取旋转角度和translateX值的正则, 例如 -> $0.style.transform.match(/rotateY\((\-?\d{1,3}\.?\d*)deg\)/) <- ["rotateY(14deg)""14"
  2.        var rotateReg = /rotateY\((\-?\d{1,3}\.?\d*)deg\)/, translateReg = /translateX\((\-?\d{1,3}\.?\d*)px\)/;  
  3.        function move(direction){ 
  4.            // 当前值为0或者当前值为卡片数目时, 返回 
  5.            if(currentIndex==(direction=='right'?0:coverCount-1))return;  
  6.            // 当前值自增或者自减 
  7.            direction=='right'?currentIndex--:currentIndex++;  
  8.            // 最大Z-index自增 
  9.            maxZIndex++;  
  10.            [].forEach.call(cards, function(element, index) { 
  11.                // 提取变换之前的旋转角度 
  12.                var previousRotate = parseInt(element.style.transform.match(rotateReg)[1]);  
  13.                // 提取变换之前的translateX 
  14.                var previousTranslate = parseInt(element.style.transform.match(translateReg)[1]); 
  15.                var currentRotate, currentTranslate;  
  16.                if(direction=='right'){ 
  17.                    // 计算rotatey的值 
  18.                    currentRotate = previousRotate-stepper;  
  19.                    // 计算平移的距离 
  20.                    currentTranslate = previousTranslate+(parentWidth/(coverCount+1));  
  21.                }else
  22.                    currentRotate = previousRotate+stepper;  
  23.                    currentTranslate = previousTranslate-(parentWidth/(coverCount+1));  
  24.                } 
  25.                // 写入元素属性 
  26.                element.style.transform = 'translateX(' + currentTranslate + 'px) rotateY('+ currentRotate +'deg)' 
  27.                // element.style.zIndex =  
  28.                if(index == currentIndex){ 
  29.                    element.style.flexGrow = 2;  
  30.                    // 不断写入maxZIndex, 确保翻过的元素始终在最前面 
  31.                    element.style.zIndex = maxZIndex;   
  32.                }else
  33.                    element.style.flexGrow = 1;  
  34.                } 
  35.            }); 
  36.        }  

再给按钮或者键盘增加事件监听, 这样就完成啦!

总结一下:

  1. flex-box可以让你的元素变得flex, 轻松实现根据元素数目重载! 各种属性让你任意操作flex元素!
  2. transform实现漂亮的3D变换效果!
  3. -webkit-box-reflect实现更加酷炫的倒影效果!
  4. 最后JS来补刀, 让我们的卡片们动起来!
作者:hymin
来源:51CTO
相关文章
|
9天前
|
JavaScript 前端开发
页面滚动触发css3动画js插件
delighters.js是一款页面滚动触发css3动画js插件。该js插件可以在页面向下滚动时,为进入浏览器视口的元素制作各种炫酷的CSS3动画效果。
37 13
|
18天前
纸屑飘落生日蛋糕场景js+css3动画特效
纸屑飘落生日蛋糕CSS3动画特效是一款js+css3制作的全屏纸屑飘落,生日蛋糕点亮庆祝动画特效。
35 3
|
1月前
|
前端开发 JavaScript
如何在 JavaScript 中访问和修改 CSS 变量?
【10月更文挑战第28天】通过以上方法,可以在JavaScript中灵活地访问和修改CSS变量,从而实现根据用户交互、页面状态等动态地改变页面样式,为网页添加更多的交互性和动态效果。在实际应用中,可以根据具体的需求和场景选择合适的方法来操作CSS变量。
|
1月前
|
缓存 前端开发 JavaScript
优化CSS和JavaScript加载
优化CSS和JavaScript加载
|
1月前
|
缓存 前端开发 JavaScript
优化CSS和JavaScript加载
Next.js和Nuxt.js在优化CSS和JavaScript加载方面提供了多种策略和工具。Next.js通过代码拆分、图片优化和特定的CSS/JavaScript优化措施提升性能;Nuxt.js则通过代码分割、懒加载、预渲染静态页面、Webpack配置和服务端缓存来实现优化。两者均能有效提高应用性能。
|
1月前
|
前端开发 JavaScript
用HTML CSS JS打造企业级官网 —— 源码直接可用
必看!用HTML+CSS+JS打造企业级官网-源码直接可用,文章代码仅用于学习,禁止用于商业
140 1
|
1月前
|
前端开发 JavaScript 安全
HTML+CSS+JS密码灯登录表单
通过结合使用HTML、CSS和JavaScript,我们创建了一个带有密码强度指示器的登录表单。这不仅提高了用户体验,还帮助用户创建更安全的密码。希望本文的详细介绍和代码示例能帮助您在实际项目中实现类似功能,提升网站的安全性和用户友好性。
48 3
|
1月前
|
前端开发 JavaScript UED
如何使用 JavaScript 动态修改 CSS 变量的值?
【10月更文挑战第28天】使用JavaScript动态修改CSS变量的值可以为页面带来更丰富的交互效果和动态样式变化,根据不同的应用场景和需求,可以选择合适的方法来实现CSS变量的动态修改,从而提高页面的灵活性和用户体验。
|
1月前
JS+CSS3文章内容背景黑白切换源码
JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
20 0
|
3月前
|
JavaScript 前端开发
JS配合CSS3实现动画和拖动小星星小Demo
本文通过代码示例展示了如何使用JavaScript和CSS3实现动画效果和拖动小星星的交互效果,包括文字掉落动画和鼠标拖动产生小星星动画的实现方法。
58 0