CSS3+JS 實現超炫的雜湊畫廊特效
下面來介紹下我按照慕課網上的視訊講解實現的照片牆效果圖。
- 當點選某張圖片時,該圖片移到中間區域並放大顯示。當圖片被點選時正反面切換顯示。
- 某張圖片被點選時,所有的圖片的位置被隨機重排
- 某個控制按鈕被點選時,對應的圖片顯示到正中間,控制按鈕進行相應的樣式切換。當連續點選某個控制按鈕時,圖片伴隨著按鈕的點選進行正反面切換
按照計算機能理解的方式來分解案例。
- View視覺 : HTML + css 基本介面模板
- Controller : Javascript 內容處理、事件處理
- Data資料 :data.js 非必須,助於理解
Data資料是很常用的,如果將內容寫死到View即HTML中,當要去改變內容的時候就得修改HTML,但是使用VCD的話就只需要修改資料部分就可以了。同時,一般資料部分是由後臺生成的,這樣的替換就更為方便。這個案例中的海報都是由模板加上資料生成的。
View 部分
對效果區域進行模組分解,分解為如下三個部分:
- 當前展現的海報區
允許被<控制條按鈕>控制展現&翻轉
- 左右存放區域
作用是存放其它的海報
每個海報位置隨機,角度隨機
- 控制條區
翻面切換<當前展現海報>正反面
實現的html程式碼如下:
這裡的{{img}}、{{caption}}、{{desc}}就是模板字串,後面會資料部分進行相應但是替換。<body onselectstart = 'return false;'><!--這裡防止頁面的文字被選中--> <!-- 2.改寫檢視為模板字串 --> <div class="wrap" id="wrap"> <!-- div.photo負責平移和旋轉 --> <div class="photo photo-front" onclick = "turn(this)" id="photo_{{index}}"> <!-- div.photo-wrap 負責正反面翻轉--> <div class="photo-wrap"> <div class="side side-front"> <p class="image"><img src="photo/{{img}}" alt=""></p> <p class="caption">{{caption}}</p> </div> <div class="side side-back"> <p class="desc">{{desc}}</p> </div> </div> </div> </div>
view部分的樣式如下:
<style type="text/css"> *{ padding:0; margin:0; } body{ background-color:#fff; color:#555; font-family:'Avenir Next','Lantinghei SC'; font-size:14px; -moz-font-smoothing:antialiased; -webkit-font-smoothing:antialiased;/*字型平滑*/ } .wrap{ //圖片區域在body中垂直居中 width:100%; height:900px; position:absolute; /*-------------------下面兩行實現了垂直居中------------- top:50%; margin-top:-300px; /*----------下面這四行也是實現垂直居中的一種方式-----------------*/ top:0; bottom:0; margin-top:auto; margin-bottom:auto; /*------------------------------------------------------------*/ background-color:#333; overflow:hidden; -moz-perspective:800px; -webkit-perspective:800px;//讓子元素獲得3D元素支援,這裡是設定子元素距離檢視的位置 } /*海報樣式*/ .photo{ width:260px; height:320px; /*因為每個海報都是利用top和left隨機定位的,所以將position設定為absolute*/ position:absolute; z-index:1; box-shadow:0 0 1px rgba(0,0,0,.01); /*transform:rotateY(30deg);*/ -moz-transition:all .6s; /*讓海報移動產生動畫效果*/ -webkit-transition:all .6s; } .photo .side{ width:100%; height:100%; background-color:#eee; position:absolute; top:0; right:0; padding:20px; box-sizing:border-box; } .photo .side-front .image{ width:100%; height:250px; line-height:250px; overflow:hidden; } .photo .side-front .image img{ width:100%; } .photo .side-front .caption{ text-align:center; font-size:16px; line-height:50px; } .photo .side-back .desc{ color:#666; font-size:14px; line-height:1.5em; } /*當前選中的海報樣式*/ .photo_center{ /*---實現垂直居中的方式一 left:50%; top:50%; margin-left:-130px; margin-top:-160px; */ /*實現垂直居中的方式二*/ top:0; bottom:0; left:0; right:0; margin:auto; z-index:999;//讓當前選中的海報不被其他的海報覆蓋 } /*負責翻轉*/ .photo-wrap{ position:absolute; width:100%; height:100%; -moz-transform-style:perserve-3d; -webkit-transform-style:preserve-3d;/*讓裡面的元素支援3d的效果*/ transform-style:preserve-3d;/*因為掉了這一句,firefox中的背面一直顯示不出來*/ -webkit-transition:all 1s; -moz-transition:all 1s; transition:all 1s; } .photo-wrap .side{ -moz-backface-visibility:hidden;//當螢幕不面向螢幕時被隱藏 -webkit-backface-visibility:hidden; backface-visibility:hidden; } .photo-wrap .side-front{ -moz-transform:rotateY(0deg); -webkit-transform:rotateY(0deg); transform:rotateY(0deg); } .photo-wrap .side-back{ -moz-transform:rotateY(180deg); -webkit-transform:rotateY(180deg); transform:rotateY(180deg); } .photo-front .photo-wrap{ -moz-transform:rotateY(0deg); -webkit-transform:rotateY(0deg); transform:rotateY(0deg); } .photo-back .photo-wrap{ -moz-transform:rotateY(180deg); -webkit-transform:rotateY(180deg); transform:rotateY(180deg); } /*控制按鈕的樣式*/ .nav{ width:40%; height:30px; line-height:30px; position:absolute; left:30%; bottom:20px; z-index:999; /*background-color:#fff;*/ text-align:center; } /*普通樣式*/ .nav .i{ width:30px; height:30px; display:inline-block; cursor:pointer; background-color:#aaa; text-align:center; border-radius:50%; -moz-transform:scale(.48); -webkit-transform:scale(.48); transform:scale(.48); -webkit-transition:all 1s; -moz-transition:all 1s; } .nav .i:after{ } /*當前選中樣式*/ .nav .i_current{ -moz-transform:scale(1); -webkit-transform:scale(1); } .nav .i_current:after{ opacity:1; } /*背面樣式*/ .nav .i_back{ -moz-transform:rotateY(-180deg); -webkit-transform:rotateY(-180deg); background-color:#555; } /*樣式優化,*/ .photo{/*定義下面的是為了消除圖片突然的閃動*/ left:50%; top:50%; margin:-160px 0 0 -130px; } .photo-wrap{ -moz-transform-origin:0% 50%; -webkit-transform-origin:0% 50%; } .photo-front .photo-wrap{ -moz-transform:translate(0px,0px) rotateY(0deg); -webkit-transform:translate(0px,0px) rotateY(0deg); } .photo-back .photo-wrap{ -moz-transform:translate(260px,0px) rotateY(180deg); -webkit-transform:translate(260px,0px) rotateY(180deg); }
下面來解釋下樣式中的一些重要部分。
案例中的設定:-webkit-perspective:800px;
可以看到設定這個後再設定rotateY(45deg)的效果,如果不設定-webkit-transform,那麼旋轉的效果便顯示不出來,圖片的旋轉只不過是在平面上進行旋轉。如果將它的值設定的太小,效果便會如下:
div.photo負責圖片的平移和旋轉,而div.photo-wrap負責3D翻轉(正反面切換)。為了讓裡面的元素支援3D效果給div.photo-wrap設定 -webkit-transform-style:preserve-3d。
.photo-wrap .side樣式中 -webkit-backface-visibility:hidden;這一句的作用是當元素不面向螢幕時進行隱藏。
為了使圖片的移動和翻轉具有動畫效果,給.photo-wrap設定-webkit-transition:下面來看下transition的解釋說明:
導航條的樣式
/*普通樣式*/
.nav .i{
width:30px;
height:30px;
display:inline-block;
cursor:pointer;
background-color:#aaa;
text-align:center;
border-radius:50%;
-moz-transform:scale(.48);
-webkit-transform:scale(.48);
transform:scale(.48);
-webkit-transition:all 1s;
-moz-transition:all 1s;
}
/*當前選中樣式*/
.nav .i_current{
-moz-transform:scale(1);
-webkit-transform:scale(1);
}
.nav .i_current:after{
opacity:1;
}
/*背面樣式*/
.nav .i_back{
-moz-transform:rotateY(-180deg);
-webkit-transform:rotateY(-180deg);
background-color:#555;
}
導航條的分析圖:
導航條的按鈕也分為正面和反面,為了區分正面和反面的效果,分別給他們設定不同的背景顏色,同時設定翻轉效果,將普通的按鈕縮小 -transform:scale(.48),當按鈕被選中時設定 scale(1)。將按鈕先放大再縮小的原因是想要精確的知道當前按鈕為選中狀態的時候會不會破壞整體的樣式。
基本的樣式都設定好後就對效果進行一些優化。為了讓圖片進行正反面切換時使圖片看起來彷彿像一扇門一樣稍微向右偏移了一點,可以增加如下的樣式:
/*樣式優化,*/
.photo{/*定義下面的是為了消除圖片突然的閃動*/
left:50%;
top:50%;
margin:-160px 0 0 -130px;
}
.photo-wrap{
-moz-transform-origin:0% 50%;
-webkit-transform-origin:0% 50%;
}
.photo-front .photo-wrap{
-moz-transform:translate(0px,0px) rotateY(0deg);
-webkit-transform:translate(0px,0px) rotateY(0deg);
}
.photo-back .photo-wrap{
-moz-transform:translate(260px,0px) rotateY(180deg);
-webkit-transform:translate(260px,0px) rotateY(180deg);
}
Data部分
資料主要寫在data.js中,data.js中定義了一個data陣列,用來存放22張圖片的資訊。data陣列輸出的結構如下:
data中存放的每個物件包括caption、desc、img這三個屬性。data中存放的每個物件的內容輸出如下。data資料部分主要用來對模板中的字串進行替換。
data.js的內容如下:
var data = [];
var dataStr = '1、照片1<br>\
<br>\
綠色蔬菜<br>\
<br>\
<br>\
2、照片2<br>\
<br>\
照片2<br>\
<br>\
<br>\
3、照片3<br>\
<br>\
照片3<br>\
<br>\
<br>\
4、照片4<br>\
<br>\
照片4<br>\
<br>\
<br>\
5、照片5<br>\
<br>\
照片5<br>\
<br>\
<br>\
6、一隻超可愛的小熊玩具<br>\
<br>\
一隻超可愛的小熊玩具<br>\
<br>\
<br>\
7、照片7<br>\
<br>\
照片7<br>\
<br>\
<br>\
8、照片8<br>\
<br>\
照片8<br>\
<br>\
<br>\
9、照片9<br>\
<br>\
照片9<br>\
<br>\
<br>\
10、照片10<br>\
<br>\
照片10<br>\
<br>\
<br>\
11、照片11<br>\
<br>\
照片11<br>\
<br>\
<br>\
12、照片12<br>\
<br>\
照片12<br>\
<br>\
<br>\
13、照片13<br>\
<br>\
照片13<br>\
<br>\
<br>\
14、照片14<br>\
<br>\
照片14<br>\
<br>\
<br>\
15、照片15<br>\
<br>\
照片15<br>\
<br>\
<br>\
16、照片16<br>\
<br>\
照片16<br>\
<br>\
<br>\
17、照片17<br>\
<br>\
照片17<br>\
<br>\
<br>\
18、照片18<br>\
<br>\
照片18<br>\
<br>\
<br>\
19、照片19<br>\
<br>\
照片19<br>\
<br>\
<br>\
20、照片20<br>\
<br>\
照片20<br>\
<br>\
<br>\
21、照片21<br>\
<br>\
照片21<br>\
<br>\
<br>\
22、照片22<br>\
<br>\
照片22<br>\
';
//下面的程式碼是將dataStr中的內容拆分存放到data陣列中
var d = dataStr.split('<br><br><br>');
for(var i = 0;i < d.length; i++) {
var c = d[i].split('<br><br>');
data.push({
img:'img'+(i+1)+'.jpg',
caption:c[0].split('、')[1],
desc:c[1]
});
}
VCD分解--Controller控制
- 輸出所有海報內容(檢視模板+資料)
- 位置分配控制(中央位置、兩邊位置)
- 控制條輸出&控制(切換、翻面)
//4.輸出所有的海報
var data = data;
function addPhotos(){
var template = $('#wrap').html();
var html = [];
var nav = [];
for(s in data) {
var _html = template.replace('{{index}}',s)
.replace('{{img}}',data[s].img)
.replace('{{caption}}',data[s].caption)
.replace('{{desc}}',data[s].desc);
html.push(_html);
// 每一個海報都有一個對應的按鈕
nav.push('<span id="nav_'+s+'" onclick="turn(this)" class="i" > </span>');
}
//遍歷完之後進行回寫
html.push('<div class="nav">'+nav.join('')+'</div>');
$('#wrap').html(html.join(''));
rsort(random([0,data.length]));
}
海報排序,分析的圖如下:
對左右分割槽的海報進行排序時,首先對左右分割槽的海報的位置分析如下:
利用隨機數生成當前海報的位置left和top值
//隨機生成一個值,支援取值範圍.random([min,max]);
function random(range){
var max = Math.max(range[0],range[1]);
var min = Math.min(range[0],range[1]);
var diff = max-min;
var number = Math.ceil(Math.random()*diff + min);
return number;
}
//6.計算左右分割槽的範圍{left:{x:[min,max],y[min,max]},right{x:[min,max],y:[min,max] }}
function range() {
var ran = {
left:{
x:[],y:[]
},
right:{
x:[],y:[]
}
};
var wrap = {
w:$('#wrap').width(),
//w:600,
h:$('#wrap').height()
}
var photo = {
w:$('.photo')[0].clientWidth,
h:$('.photo')[0].clientHeight
}
ran.left.x = [0 - photo.w,wrap.w/2 - photo.w/2];
ran.left.y = [0 - photo.h,wrap.h];
ran.right.x = [wrap.w/2 + photo.w/2,wrap.w + photo.w];
ran.right.y = ran.left.y;
return ran;
}
接著就可以對海報進行排序
//5.排序海報
function rsort(n) {
var _photo = $('.photo');
var photos = [];
for(var i = 0;i < _photo.length;i++) {
_photo[i].className = _photo[i].className.replace(/\s*photo_center\s*/,'');
_photo[i].className = _photo[i].className.replace(/\s*photo-front\s*/,'');
_photo[i].className = _photo[i].className.replace(/\s*photo-back\s*/,'');
//因為上面把 photo-front和photo-back都替換掉了,
_photo[i].className += ' photo-front';
_photo[i].style.left = '';
_photo[i].style.top = '';
_photo[i].style['-moz-transform'] =_photo[i].style['transform'] = _photo[i].style['-webkit-transform'] = 'rotate(0deg) scale(1.3)';
photos.push(_photo[i]);
}
//var photo_center = $('#photo_'+n)[0];
var photo_center = document.getElementById('photo_'+n);
var newClass = photo_center.className + ' photo_center';
//console.log(photo_center.attr('class'));
photo_center = photos.splice(n,1)[0];
//photo_center.className = newClass;
$('#photo_'+n).attr('class',newClass);
//把剩下的海報分為兩個部分
var photos_left = photos.splice(0,Math.ceil(photos.length/2));
var photos_right = photos;
var ranges = range();
//左分割槽排序
for(s in photos_left) {
var photo = photos_left[s];
photo.style.left = random(ranges.left.x) + 'px';
photo.style.top = random(ranges.left.y) + 'px';
photo.style['-moz-transform'] =photo.style['transform'] = photo.style['-webkit-transform'] = 'rotate('+random([-150,150])+'deg) scale(1)';
}
//右分割槽排序
for(s in photos_right) {
var photo = photos_right[s];
photo.style.left = random(ranges.right.x) + 'px';
photo.style.top = random(ranges.right.y) + 'px';
photo.style['-moz-transform'] =photo.style['transform'] = photo.style['-webkit-transform'] = 'rotate('+random([-100,100])+'deg) scale(1)';
}
//控制按鈕的處理
var navs = $('.i');
for(var i = 0;i<navs.length;i++) {
navs[i].className = navs[i].className.replace(/\s*i_current\s*/,'');
navs[i].className = navs[i].className.replace(/\s*i_back\s*/,'');
}
//給對應當前海報的按鈕增加 i_current樣式
$('#nav_'+n)[0].className += ' i_current';
}
海報正反面切換及控制按鈕正煩面切換效果:
function turn(elem) {
var cls = elem.className;
var n = elem.id.split('_')[1];
if(!(/photo_center/.test(cls))) {
rsort(n);
}
//上面的if語句執行完後className被改變了,由於我沒有意思到這一點
//導致總是缺少photo_center這個class
var cs = $('#photo_'+n)[0];
cls = cs.className;
if(/photo-front/.test(cls)) {
cls = cls.replace(/photo-front/,'photo-back');
$('#nav_'+n)[0].className += ' i_back';
}else {
cls = cls.replace(/photo-back/,'photo-front')
$('#nav_'+n)[0].className = $('#nav_'+n)[0].className.replace(/\s*i_back\s*/,'');
}
cs.className = cls;
}
可以根據rsort(n)中的n去了解到底哪一個海報是當前展現的海報,也能知道哪一個按鈕是當前選中的按鈕。
上述即為相應的效果分析。
總結:
分析案例的思路方法:- 模組分析法
- VCD分析法
一些新的CSS效果
- 3D檢視位置設定 & 子元素3D支援
- 翻轉不可見時隱藏
- 使用CSS旋轉(Y軸)和位移
- CSS切換動畫
前端指令碼技巧
- 字串替換的簡易模板功能
- 根據範圍獲得一些隨機數
- 使用指令碼切換元素的className 以及具體的style屬性
.photo-wrap .side{
-moz-backface-visibility:hidden;
-webkit-backface-visibility:hidden;
backface-visibility:hidden;
}
原來是:我漏寫了transform-style:preserve-3d。所以在瀏覽器相容時,記得寫不加字首的樣式
.photo-wrap{
position:absolute;
width:100%;
height:100%;
-moz-transform-style:perserve-3d;
-webkit-transform-style:preserve-3d;/*讓裡面的元素支援3d的效果*/
<span style="color:#cc0000;">transform-style:preserve-3d;/*因為掉了這一句,firefox中的背面一直顯示不出來*/</span>
-webkit-transition:all 1s;
-moz-transition:all 1s;
transition:all 1s;
}
最後附上完整的js程式碼:
<script type="text/javascript">
//隨機生成一個值,支援取值範圍.random([min,max]);
function random(range){
var max = Math.max(range[0],range[1]);
var min = Math.min(range[0],range[1]);
var diff = max-min;
var number = Math.ceil(Math.random()*diff + min);
return number;
}
//4.輸出所有的海報
var data = data;
function addPhotos(){
var template = $('#wrap').html();
var html = [];
var nav = [];
for(s in data) {
var _html = template.replace('{{index}}',s)
.replace('{{img}}',data[s].img)
.replace('{{caption}}',data[s].caption)
.replace('{{desc}}',data[s].desc);
html.push(_html);
// 每一個海報都有一個對應的按鈕
nav.push('<span id="nav_'+s+'" onclick="turn(this)" class="i" > </span>');
}
html.push('<div class="nav">'+nav.join('')+'</div>');
$('#wrap').html(html.join(''));
rsort(random([0,data.length]));
}
addPhotos()
//6.計算左右分割槽的範圍{left:{x:[min,max],y[min,max]},right{x:[min,max],y:[min,max] }}
function range() {
var ran = {
left:{
x:[],y:[]
},
right:{
x:[],y:[]
}
};
var wrap = {
w:$('#wrap').width(),
//w:600,
h:$('#wrap').height()
}
var photo = {
w:$('.photo')[0].clientWidth,
h:$('.photo')[0].clientHeight
}
ran.left.x = [0 - photo.w,wrap.w/2 - photo.w/2];
ran.left.y = [0 - photo.h,wrap.h];
ran.right.x = [wrap.w/2 + photo.w/2,wrap.w + photo.w];
ran.right.y = ran.left.y;
return ran;
}
//5.排序海報
function rsort(n) {
var _photo = $('.photo');
var photos = [];
for(var i = 0;i < _photo.length;i++) {
_photo[i].className = _photo[i].className.replace(/\s*photo_center\s*/,'');
_photo[i].className = _photo[i].className.replace(/\s*photo-front\s*/,'');
_photo[i].className = _photo[i].className.replace(/\s*photo-back\s*/,'');
//因為上面把 photo-front和photo-back都替換掉了,
_photo[i].className += ' photo-front';
_photo[i].style.left = '';
_photo[i].style.top = '';
_photo[i].style['-moz-transform'] =_photo[i].style['transform'] = _photo[i].style['-webkit-transform'] = 'rotate(0deg) scale(1.3)';
photos.push(_photo[i]);
}
//var photo_center = $('#photo_'+n)[0];
var photo_center = document.getElementById('photo_'+n);
var newClass = photo_center.className + ' photo_center';
//console.log(photo_center.attr('class'));
photo_center = photos.splice(n,1)[0];
//photo_center.className = newClass;
$('#photo_'+n).attr('class',newClass);
//把剩下的海報分為兩個部分
var photos_left = photos.splice(0,Math.ceil(photos.length/2));
var photos_right = photos;
var ranges = range();
//左分割槽排序
for(s in photos_left) {
var photo = photos_left[s];
photo.style.left = random(ranges.left.x) + 'px';
photo.style.top = random(ranges.left.y) + 'px';
photo.style['-moz-transform'] =photo.style['transform'] = photo.style['-webkit-transform'] = 'rotate('+random([-150,150])+'deg) scale(1)';
}
//右分割槽排序
for(s in photos_right) {
var photo = photos_right[s];
photo.style.left = random(ranges.right.x) + 'px';
photo.style.top = random(ranges.right.y) + 'px';
photo.style['-moz-transform'] =photo.style['transform'] = photo.style['-webkit-transform'] = 'rotate('+random([-100,100])+'deg) scale(1)';
}
//控制按鈕的處理
var navs = $('.i');
for(var i = 0;i<navs.length;i++) {
navs[i].className = navs[i].className.replace(/\s*i_current\s*/,'');
navs[i].className = navs[i].className.replace(/\s*i_back\s*/,'');
}
//給對應當前海報的按鈕增加 i_current樣式
$('#nav_'+n)[0].className += ' i_current';
}
function turn(elem) {
var cls = elem.className;
var n = elem.id.split('_')[1];
if(!(/photo_center/.test(cls))) {
rsort(n);
}
//上面的if語句執行完後className被改變了,由於我沒有意思到這一點
//導致總是缺少photo_center這個class
var cs = $('#photo_'+n)[0];
cls = cs.className;
if(/photo-front/.test(cls)) {
cls = cls.replace(/photo-front/,'photo-back');
$('#nav_'+n)[0].className += ' i_back';
}else {
cls = cls.replace(/photo-back/,'photo-front')
$('#nav_'+n)[0].className = $('#nav_'+n)[0].className.replace(/\s*i_back\s*/,'');
}
cs.className = cls;
}
</script>