CSS+JS實現蘋果cover flow效果

廢話很少說, 直接上最終效果圖和代碼吧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>

稍微解釋下這裏用到的幾個知識點:前端

1. flex-box.

什麼是flex-box捏, 它是爲了適應當前設備屏幕大小不一而提出的一種display方法. 當一個父元素的顯示被設定爲display:flex時, 它內部的子元素們會被平均分配佔滿父元素的空間, 而且當父元素的尺寸變化時, 子元素的尺寸也會相應變化! 是否是很神奇呢? 不只如此, 你還能夠任意分配子元素們的排列順序, 若是以爲某個子元素須要突出顯示, 就能夠給這個子元素以特殊身份, 讓它相比其餘子元素大一些, 或者小一些! 因爲其的自適應特性, flex是移動開發的一把利器, 咱們先來看看一個小應用: java

設計一個對話框函數, 當傳入一個回調函數時, 只顯示一個'肯定'按鈕, 佔100%寬度; 當傳入兩個回調函數(肯定和取消)時, 分別顯示'肯定'和'取消'按鈕, 各佔50%寬度, 樣式分別以下: git

clipboard.png

clipboard.png

怎麼實現呢? 咱們固然能夠用JS來作, 可是(凡事就怕一個可是哈哈)! 咱們做爲有追求的前端, 戰鬥在CSS探索的第一線, 如今有了如此好用的flex屬性, 爲毛不立馬用起來呢? 說走咱就走, 按鈕容器和按鈕自己的CSS以下: github

clipboard.png

clipboard.png

關鍵是按鈕的width:100%屬性. 有了它, 當容器裏只有一個按鈕時, 它的寬度會拓展爲容器的100%寬度; 而當容器裏有兩個按鈕時, 按鈕的寬度都爲100%, 怎麼辦呢? 因爲兩個按鈕平分秋色, 它們只好勢均力敵, 各佔50%的空間了! web

有的同窗要問了: 要是我不想按鈕把空間佔滿怎麼辦呢? 這時候, 能夠設定按鈕的寬度各爲45%, 而後在父元素上設置justify-content:center, 意思是兩個子元素只佔了90%的橫向空間, 那怎麼分配剩下的10%空間呢? 那就兩邊各分配5%吧! 除此以外, 該屬性的其它值, 可讓子元素左右對齊, 更多flexbox的神奇應用, 請參考這篇文章~
A Complete Guide to Flexboxsegmentfault

回到咱們的例子. 在卡片們沒有被應用transform屬性以前, 它們看起來是這個樣子的: app

clipboard.png

七個元素平均分佈, 佔據了父元素的所有橫向空間. 其中中間的元素應用了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循環就能夠完成這項任務, 代碼以下:

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; 
            }
        }

初始化完成後效果圖以下:

clipboard.png

此時每一個卡片的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, 效果圖以下:

clipboard.png

然而靜態的展現是不夠的, 咱們的目標是! 要讓它動起來! 來回左右動! 到這裏CSS已經無能爲力, 改JS閃亮登場的時候了!~

4.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; 
                }
            });
        }

再給按鈕或者鍵盤增長事件監聽, 這樣就完成啦!

總結一下:

  1. flex-box可讓你的元素變得flex, 輕鬆實現根據元素數目重載! 各類屬性讓你任意操做flex元素!

  2. transform實現漂亮的3D變換效果!

  3. -webkit-box-reflect實現更加酷炫的倒影效果!

  4. 最後JS來補刀, 讓咱們的卡片們動起來!

參考資料

A Complete Guide to Flexbox
-webkit-box-reflect屬性簡介及元素鏡像倒影實現

相關文章
相關標籤/搜索