废话很少说, 直接上最终效果图和代码吧javascript
github地址: css
https://github.com/YueminHu/b...html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>coverflow-demo</title> <style> div.innerWrapper{ perspective: 300px; width: 600px; height: 300px; margin: 100px auto; display: flex; align-items:flex-start; background-color: #000; overflow: hidden; padding-top: 5%; } div.cover{ height: 50%; flex-grow:1; transition: all .5s ease; background-size: 100% 100%; background-repeat:no-repeat; margin: 0; -webkit-box-reflect:below 5% linear-gradient(transparent, white); border: 1px solid #fff; } div.cover:nth-child(1){ background-image: url('covers/computergraphics-album-covers-2014-15.jpg'); } div.cover:nth-child(2){ background-image: url('covers/Funkadelic-Maggot-Brain-album-covers-billboard-1000x1000.jpg'); } div.cover:nth-child(3){ background-image: url('covers/Green-Day-American-Idiot-album-covers-billboard-1000x1000.jpg'); } div.cover:nth-child(4){ background-image: url('covers/insurgency-digital-album-cover-design.jpg'); } div.cover:nth-child(5){ background-image: url('covers/Pink-Floyd-Dark-Side-of-the-Moon-album-covers-billboard-1000x1000.jpg'); } div.cover:nth-child(6){ background-image: url('covers/sonic-quiver-time-and-space1-1000x1000.jpg'); } div.cover:nth-child(7){ background-image: url('covers/tumblr_inline_nydppi1Mp91t7tdyh_500.jpg'); } button[required='required']{ background-color: #000; } </style> </head> <body> <div class='container'> <div class="innerWrapper"> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> </div> </div> <button required='required'>222</button> <script> ;(function(parent){ var cards = parent.querySelectorAll('div'), coverCount = cards.length, middleIndex = (coverCount-1)/2, middleCover = cards[middleIndex], parentWidth = middleCover.parentNode.clientWidth, currentIndex = middleIndex; var maxRotate = 42, stepper = maxRotate/middleIndex, maxZIndex = middleIndex + 1; var rotateReg = /rotateY\((\-?\d{1,3}\.?\d*)deg\)/, translateReg = /translateX\((\-?\d{1,3}\.?\d*)px\)/; // debugger; for(var i = 0; i<coverCount; i++){ var elem = cards[i]; elem.classList.add('cover'); elem.style.transform = 'translateX(0px) rotateY(' + (maxRotate-(i*stepper).toFixed(0)) + 'deg)'; elem.style.flexGrow = 1; if(i<middleIndex){ elem.style.zIndex = i+1; }else if(i == middleIndex){ elem.style.zIndex = i+1; elem.style.flexGrow = 2; }else{ elem.style.zIndex = coverCount - i; } } function move(direction){ if(currentIndex==(direction=='right'?0:coverCount-1))return; direction=='right'?currentIndex--:currentIndex++; maxZIndex++; [].forEach.call(cards, function(element, index) { var previousRotate = parseInt(element.style.transform.match(rotateReg)[1]); var previousTranslate = parseInt(element.style.transform.match(translateReg)[1]); // translateX + 80 px one right button is clicked var currentRotate, currentTranslate; if(direction=='right'){ currentRotate = previousRotate-stepper; currentTranslate = previousTranslate+(parentWidth/(coverCount+1)); }else{ currentRotate = previousRotate+stepper; currentTranslate = previousTranslate-(parentWidth/(coverCount+1)); } element.style.transform = 'translateX(' + currentTranslate + 'px) rotateY('+ currentRotate +'deg)' // element.style.zIndex = if(index == currentIndex){ element.style.flexGrow = 2; element.style.zIndex = maxZIndex; }else{ element.style.flexGrow = 1; } }); } document.addEventListener('keyup', function(e){ if(e.which == 37){ move('right'); }else if(e.which == 39){ move('left'); } }) })(document.querySelector('.innerWrapper')); </script> </body> </html>
稍微解释下这里用到的几个知识点:前端
什么是flex-box捏, 它是为了适应当前设备屏幕大小不一而提出的一种display
方法. 当一个父元素的显示被设定为display:flex
时, 它内部的子元素们会被平均分配占满父元素的空间, 而且当父元素的尺寸变化时, 子元素的尺寸也会相应变化! 是否是很神奇呢? 不只如此, 你还能够任意分配子元素们的排列顺序, 若是以为某个子元素须要突出显示, 就能够给这个子元素以特殊身份, 让它相比其余子元素大一些, 或者小一些! 因为其的自适应特性, flex
是移动开发的一把利器, 咱们先来看看一个小应用: java
设计一个对话框函数, 当传入一个回调函数时, 只显示一个'肯定'按钮, 占100%宽度; 当传入两个回调函数(肯定和取消)时, 分别显示'肯定'和'取消'按钮, 各占50%宽度, 样式分别以下: git
怎么实现呢? 咱们固然能够用JS来作, 可是(凡事就怕一个可是哈哈)! 咱们做为有追求的前端, 战斗在CSS探索的第一线, 如今有了如此好用的flex
属性, 为毛不立马用起来呢? 说走咱就走, 按钮容器和按钮自己的CSS以下: github
关键是按钮的width:100%
属性. 有了它, 当容器里只有一个按钮时, 它的宽度会拓展为容器的100%宽度; 而当容器里有两个按钮时, 按钮的宽度都为100%, 怎么办呢? 因为两个按钮平分秋色, 它们只好势均力敌, 各占50%的空间了! web
有的同窗要问了: 要是我不想按钮把空间占满怎么办呢? 这时候, 能够设定按钮的宽度各为45%, 而后在父元素上设置justify-content:center
, 意思是两个子元素只占了90%的横向空间, 那怎么分配剩下的10%空间呢? 那就两边各分配5%吧! 除此以外, 该属性的其它值, 可让子元素左右对齐, 更多flexbox
的神奇应用, 请参考这篇文章~
A Complete Guide to Flexboxsegmentfault
回到咱们的例子. 在卡片们没有被应用transform
属性以前, 它们看起来是这个样子的: app
七个元素平均分布, 占据了父元素的所有横向空间. 其中中间的元素应用了flex-grow:2
的属性, 使得它比其余元素高人一等, 面积是其余元素的两倍~~
其实有了上一幅图, 初始页面的雏形就已经差很少了~如今只须要给父元素设置视角(关于视角, 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循环就能够完成这项任务, 代码以下:
for(var i = 0; i<coverCount; i++){ var elem = cards[i]; elem.classList.add('cover'); // 设置元素的translateX为0px, 旋转角度为最大旋转角度-目录值*步进值 elem.style.transform = 'translateX(0px) rotateY(' + (maxRotate-(i*stepper).toFixed(0)) + 'deg)'; elem.style.flexGrow = 1; // 设置元素的z-index以区分先后顺序, 并将中间元素设置大一些 if(i<middleIndex){ elem.style.zIndex = i+1; }else if(i == middleIndex){ elem.style.zIndex = i+1; elem.style.flexGrow = 2; }else{ elem.style.zIndex = coverCount - i; } }
初始化完成后效果图以下:
此时每一个卡片的translateX
为0, 这个值要预先写好, 才能经过改变该值来实现卡片的左右移动效果; rotateY
的值分别为42, 28, 14, 0, -14, -28, 42
度; flex-grow
(相对于其它子元素的大小)分别为1, 1, 1, 2, 1, 1, 1
那么漂亮的倒影是怎么实现的呢? 哈哈其实一行CSS就能搞定, 那就是强大的-webkit-box-reflect
, 值为below 5% linear-gradient(transparent, white)
. 相信聪明的小伙伴看到这里已经明白了大概了, 为了不误解, 稍稍再解释一下~below
是倒影在盒子下方, 5%表示offset
, 和盒子的距离是盒子宽度的5%
, linear-gradient(transparent, white)
指的是倒影的颜色, 从透明到彻底不透明. 渐变语法的颜色在这里起做用的只有透明度, 白色的颜色是不会显出来的~到这里, 咱们用的大部分都是CSS, 效果图以下:
然而静态的展现是不够的, 咱们的目标是! 要让它动起来! 来回左右动! 到这里CSS已经无能为力, 改JS闪亮登场的时候了!~
控制左右移动的函数以下, 接受一个参数left
或者right
表示要移动的方向~
// 定义提取旋转角度和translateX值的正则, 例如 -> $0.style.transform.match(/rotateY\((\-?\d{1,3}\.?\d*)deg\)/) <- ["rotateY(14deg)", "14"] var rotateReg = /rotateY\((\-?\d{1,3}\.?\d*)deg\)/, translateReg = /translateX\((\-?\d{1,3}\.?\d*)px\)/; function move(direction){ // 当前值为0或者当前值为卡片数目时, 返回 if(currentIndex==(direction=='right'?0:coverCount-1))return; // 当前值自增或者自减 direction=='right'?currentIndex--:currentIndex++; // 最大Z-index自增 maxZIndex++; [].forEach.call(cards, function(element, index) { // 提取变换以前的旋转角度 var previousRotate = parseInt(element.style.transform.match(rotateReg)[1]); // 提取变换以前的translateX var previousTranslate = parseInt(element.style.transform.match(translateReg)[1]); var currentRotate, currentTranslate; if(direction=='right'){ // 计算rotatey的值 currentRotate = previousRotate-stepper; // 计算平移的距离 currentTranslate = previousTranslate+(parentWidth/(coverCount+1)); }else{ currentRotate = previousRotate+stepper; currentTranslate = previousTranslate-(parentWidth/(coverCount+1)); } // 写入元素属性 element.style.transform = 'translateX(' + currentTranslate + 'px) rotateY('+ currentRotate +'deg)' // element.style.zIndex = if(index == currentIndex){ element.style.flexGrow = 2; // 不断写入maxZIndex, 确保翻过的元素始终在最前面 element.style.zIndex = maxZIndex; }else{ element.style.flexGrow = 1; } }); }
再给按钮或者键盘增长事件监听, 这样就完成啦!
总结一下:
flex-box
可让你的元素变得flex
, 轻松实现根据元素数目重载! 各类属性让你任意操做flex
元素!
transform
实现漂亮的3D变换效果!
-webkit-box-reflect
实现更加酷炫的倒影效果!
最后JS来补刀, 让咱们的卡片们动起来!