基于CSS3的3D旋转效果
自从有了html5和css3,好多以前只能想想的华丽效果都可以上手实现了。3D 转换(个人认为3D变换更贴切^)就是其中之一。关于3D转换,可以阅读CSS3 3D transform变换,不过如此,文中对3D转换进行了形象、生动、详细的阐述。在这里,只和大家讨论怎么利用3D转换来实现立体及其旋转效果,例如:
好吧,废话不多说,上代码!
1.页面代码
1 <div class="translate3D_test"> 2 <ul class="cube"> 3 <li class="translate3D_li">1</li> 4 <li class="translate3D_li">2</li> 5 <li class="translate3D_li">3</li> 6 <li class="translate3D_li">4</li> 7 <li class="translate3D_li">5</li> 8 <li class="translate3D_li">6</li> 9 </ul> 10 <br/> 11 <a href="#" onclick="return clicka(11)">左</a> 12 <a href="#" onclick="return clicka(12)">右</a> 13 <a href="#" onclick="return clicka(13)">上</a> 14 <a href="#" onclick="return clicka(14)">下</a> 15 <a href="#" onclick="return clicka(1)">1</a> 16 <a href="#" onclick="return clicka(2)">2</a> 17 <a href="#" onclick="return clicka(3)">3</a> 18 <a href="#" onclick="return clicka(4)">4</a> 19 <a href="#" onclick="return clicka(5)">5</a> 20 <a href="#" onclick="return clicka(6)">6</a> 21 <a href="#" onclick="return clicka(0)">复原</a> 22 </div>
2.CSS代码
1 ul 2 { 3 list-style-type:none; 4 margin:0; 5 padding:0; 6 } 7 li 8 { 9 border:0; 10 } 11 .translate3D_test{ 12 position: relative; 13 width:500px; 14 height: 500px; 15 margin:auto; 16 background:#ddd; 17 } 18 19 .translate3D_li{ 20 background:#999; 21 font-size:50px; 22 border:1px solid #F15000; 23 }
3.JS代码
1 $(document).ready(function(){ 2 $(".cube").translate3D(400,0,0.5); 3 $("#notcube").translate3D(400,500,0.5); 4 }); 5 6 function clicka(who){ 7 switch(who) 8 { 9 case 0: 10 $(".cube").transform3D(0); 11 break; 12 case 1: 13 $(".cube").transform3D(1); 14 break; 15 case 2: 16 $(".cube").transform3D(2); 17 break; 18 case 3: 19 $(".cube").transform3D(3); 20 break; 21 case 4: 22 $(".cube").transform3D(4); 23 break; 24 case 5: 25 $(".cube").transform3D(5); 26 break; 27 case 6: 28 $(".cube").transform3D(6); 29 break; 30 case 11: // left 31 $(".cube").transform3D("left"); 32 break; 33 case 12: // right 34 $(".cube").transform3D("right"); 35 break; 36 case 13: // up 37 $(".cube").transform3D("up"); 38 break; 39 case 14: // down 40 $(".cube").transform3D("down"); 41 break; 42 default:break; 43 }; 44 return false; 45 } 46 47 function clickb(who){ 48 switch(who) 49 { 50 case 0: 51 $("#notcube").transform3D(0); 52 break; 53 case 1: 54 $("#notcube").transform3D(1); 55 break; 56 case 2: 57 $("#notcube").transform3D(2); 58 break; 59 case 3: 60 $("#notcube").transform3D(3); 61 break; 62 case 4: 63 $("#notcube").transform3D(4); 64 break; 65 case 11: // left 66 $("#notcube").transform3D("left"); 67 break; 68 case 12: // right 69 $("#notcube").transform3D("right"); 70 break; 71 case 13: // up 72 $("#notcube").transform3D("up"); 73 break; 74 default:break; 75 }; 76 return false; 77 } 78 79 /* 80 * translate3D 1.0 81 * Copyright (c) 2014 BowenLuo http://www.luobo.com/ 82 * Date: 2014-06-04 83 * 基于Html5和CSS3的立体旋转效果实现,支持循环旋转,支持指定面的显示 84 * Example: $(".cube").translate3D(400,0,0.5);//设置类为cube的元素立方体效果,400为长宽高,当第一个参数为0时为正方体效果,0.5为旋转时间,单位为s 85 * $("#notcube").translate3D(400,500,0.5);//设置ID为notcube的元素立方体效果,400为长宽,500为高,0.5为旋转时间,单位为s 86 * $(".cube").transform3D("left");//设置类为cube的立体元素向左旋转90° 87 * $("#notcube").transform3D(5);//设置ID为notcube的立体元素旋转后显示第5个面,即顶部界面 88 * 注意:1.插件需jquery支持,暂时只支持Chrome ,Safari ,Firefox 浏览器; 89 * 2.面元素和元素容器推荐使用div标签,并请确保至少有4个面元素(正方体效果支持6个面,柱体效果支持4个面); 90 * 3.当不为正方体效果或面元素少于6个时,请不要使用上下旋转和显示第5、6个面,以确保良好的用户体验; 91 * 4.同一个页面可以设置多个标签的立体效果,但暂时只完美支持对一个立方体进行旋转操作; 92 * 5.在进行旋转动作之前,请设置元素的立体效果(在文档加载完成之后设置)。 93 */ 94 (function($){ 95 $.fn.translate3D = function(width,height,time){ 96 isCube = false; 97 if(height==0){ 98 isCube = true; 99 } 100 var thisEle=$(this); 101 if(isCube){ 102 $(thisEle).css({"width":width,"height":width,"overflow":"visible"}); 103 $(thisEle).children().css({"position": "absolute","width":width,"height":width}); 104 }else{ 105 $(thisEle).css({"width":width,"height":height,"overflow":"visible"}); 106 $(thisEle).children().css({"position": "absolute","width":width,"height":height}); 107 } 108 $(thisEle).css({"-webkit-transition":"-webkit-transform "+time+"s linear","-webkit-transform-style":"preserve-3d"}); 109 $(thisEle).css({"-moz-transition":"-moz-transform "+time+"s linear","-moz-transform-style":"preserve-3d"}); 110 $(thisEle).children().css({"-webkit-backface-visibility":"hidden","-moz-backface-visibility":"hidden"}); 111 var mcpara = width/2; 112 var translateZ1 = "translateZ("+mcpara+"px)"; 113 var translateZ2 = "rotateY(90deg) translateZ("+mcpara+"px)"; 114 var translateZ3 = "rotateY(180deg) translateZ("+mcpara+"px)"; 115 var translateZ4 = "rotateY(-90deg) translateZ("+mcpara+"px)"; 116 var translateZ5,translateZ6; 117 $(thisEle).children("*:eq(0)").css({"-webkit-transform":translateZ1}); 118 $(thisEle).children("*:eq(1)").css({"-webkit-transform":translateZ2}); 119 $(thisEle).children("*:eq(2)").css({"-webkit-transform":translateZ3}); 120 $(thisEle).children("*:eq(3)").css({"-webkit-transform":translateZ4}); 121 122 $(thisEle).children("*:eq(0)").css({"-moz-transform":translateZ1}); 123 $(thisEle).children("*:eq(1)").css({"-moz-transform":translateZ2}); 124 $(thisEle).children("*:eq(2)").css({"-moz-transform":translateZ3}); 125 $(thisEle).children("*:eq(3)").css({"-moz-transform":translateZ4}); 126 if(isCube){ 127 translateZ5 = "rotateX(90deg) translateZ("+mcpara+"px) " ; 128 translateZ6 = "rotateX(-90deg) translateZ("+mcpara+"px) "; 129 $(thisEle).children("*:gt(5)").css({"display":"none"}); 130 $(thisEle).children("*:eq(4)").css({"-webkit-transform":translateZ5}); 131 $(thisEle).children("*:eq(5)").css({"-webkit-transform":translateZ6}); 132 $(thisEle).children("*:eq(4)").css({"-moz-transform":translateZ5}); 133 $(thisEle).children("*:eq(5)").css({"-moz-transform":translateZ6}); 134 }else{ 135 $(thisEle).children("*:gt(3)").css({"display":"none"}); 136 } 137 }; 138 139 $.fn.transform3D = function(direction){ 140 if(typeof(yAngle)!=='number'){ 141 yAngle = 0; 142 } 143 if(typeof(xAngle)!=='number'){ 144 xAngle = 0; 145 } 146 var thisEle=$(this); 147 var basexAngle = Math.round((xAngle%360)/360)*360+(xAngle-xAngle%360); 148 /* 149 if(Math.abs(xAngle%360)>180){ 150 if(xAngle>0){ 151 basexAngle=xAngle-xAngle%360+360; 152 } 153 if(xAngle<0){ 154 basexAngle=xAngle-xAngle%360-360; 155 } 156 }else{ 157 basexAngle=xAngle-xAngle%360; 158 }*/ 159 if(typeof(direction)=='number'){ 160 161 switch(direction) 162 { 163 case 0: 164 xAngle = 0; 165 yAngle = 0; 166 break; 167 case 1: 168 xAngle = basexAngle; 169 if(Math.abs(yAngle%360)>180){ 170 if(yAngle>0){ 171 yAngle = yAngle-yAngle%360+360; 172 } 173 if(yAngle<0){ 174 yAngle = yAngle-yAngle%360-360; 175 } 176 177 }else{ 178 yAngle=yAngle-yAngle%360; 179 } 180 break; 181 case 2: 182 xAngle = basexAngle; 183 if(Math.abs(-yAngle%360-90)>180){ 184 if(yAngle>0){ 185 yAngle = yAngle-yAngle%360-90+360; 186 } 187 if(yAngle<0){ 188 yAngle = yAngle-yAngle%360-90-360; 189 } 190 191 }else{ 192 yAngle=yAngle-yAngle%360-90; 193 } 194 break; 195 case 3: 196 xAngle = basexAngle; 197 if(Math.abs(-yAngle%360-180)>180){ 198 if(yAngle>0){ 199 yAngle = yAngle-yAngle%360-180+360; 200 } 201 if(yAngle<0){ 202 yAngle = yAngle-yAngle%360-180-360; 203 } 204 205 }else{ 206 yAngle=yAngle-yAngle%360-180; 207 } 208 break; 209 case 4: 210 xAngle = basexAngle; 211 Math.abs(xAngle%360)/360 212 if(Math.abs(-yAngle%360+90)>180){ 213 if(yAngle>0){ 214 yAngle = yAngle-yAngle%360+90+360; 215 } 216 if(yAngle<0){ 217 yAngle = yAngle-yAngle%360+90-360; 218 } 219 }else{ 220 yAngle=yAngle-yAngle%360+90; 221 } 222 break; 223 case 5: 224 xAngle = basexAngle-90; 225 if(Math.abs(yAngle%360)>180){ 226 if(yAngle>0){ 227 yAngle = yAngle-yAngle%360+360; 228 } 229 if(yAngle<0){ 230 yAngle = yAngle-yAngle%360-360; 231 } 232 }else{ 233 yAngle=yAngle-yAngle%360; 234 } 235 break; 236 case 6: 237 xAngle = basexAngle+90; 238 if(Math.abs(yAngle%360)>180){ 239 if(yAngle>0){ 240 yAngle = yAngle-yAngle%360+360; 241 } 242 if(yAngle<0){ 243 yAngle = yAngle-yAngle%360-360; 244 } 245 246 }else{ 247 yAngle=yAngle-yAngle%360; 248 } 249 break; 250 default:break; 251 }; 252 } 253 if(direction=='left'){ 254 xAngle = basexAngle; 255 yAngle -= 90; 256 } 257 if(direction=='right'){ 258 xAngle = basexAngle; 259 yAngle += 90; 260 } 261 if(direction=='up'){ 262 if(xAngle%360==0){ 263 xAngle += 90; 264 }else{ 265 xAngle += 180; 266 } 267 if(Math.abs(yAngle%360)>180){ 268 if(yAngle>0){ 269 yAngle = yAngle-yAngle%360+360; 270 } 271 if(yAngle<0){ 272 yAngle = yAngle-yAngle%360-360; 273 } 274 }else{ 275 yAngle=yAngle-yAngle%360; 276 } 277 } 278 if(direction=='down'){ 279 if(xAngle%360==0){ 280 xAngle -= 90; 281 }else{ 282 xAngle -= 180; 283 } 284 if(Math.abs(yAngle%360)>180){ 285 if(yAngle>0){ 286 yAngle = yAngle-yAngle%360+360; 287 } 288 if(yAngle<0){ 289 yAngle = yAngle-yAngle%360-360; 290 } 291 }else{ 292 yAngle=yAngle-yAngle%360; 293 } 294 } 295 $(thisEle).css("transform","rotateX("+xAngle+"deg) rotateY("+yAngle+"deg)"); 296 $(thisEle).css("webkitTransform","rotateX("+xAngle+"deg) rotateY("+yAngle+"deg)"); 297 $(thisEle).css("-moz-Transform","rotateX("+xAngle+"deg) rotateY("+yAngle+"deg)"); 298 }; 299 })(jQuery);
整个过程可以分为2步,第一步:通过对面元素<li class="translate3D_li"></li>进行3D变换来搭建立方体;第二步:通过对面元素容器<ul class="cube"></ul>的3D变换来实现3D立体旋转效果。在搭建立方体时需要注意translateZ的使用,translateZ大小即为立方体中心到该面的垂直距离:
一般为面元素宽的一半。rotateX和roteteY可以理解为位置偏移量,例如右转90°,需要在原roteteY的基础上加90,即roteteY+=90,而不是roteteY=90。要想实现立体旋转效果,还必须设置动作效果transition:transform 2s linear(各浏览器之间不同),一个是偏转量,一个是动作时间,类似于left:'250px'和animate({left:'250px'})的关系。而要实现平滑的旋转效果,例如从当前面转到右侧面,可以左转90°,也可以右转270°,当然左转90°更优美。在这里,平滑就是转换的角度总是相对最小的。为了实现平滑的旋转,死了我大量脑细胞,结果差强人意,效果上没什么问题,感觉代码和算法有点复杂,如果大家有什么好的办法,不吝指教。我简单说下我的思路:
1.确定目标偏移量,并确保目标便宜量与当前偏移量之间的差值小于360;
2.计算当前偏移量和目标偏移量之间的差值,如果绝对值大于180(即270),想办法变为90;
3.综合水平和垂直偏移量进行变换。
其中,水平和垂直方向的偏移量是分别处理,互不干扰的,处理完后再结合变换,保证在水平和垂直方向上转换的角度都达到最小。
PS:1.上面的js部分,从多行注释开始,是我自己写的jquery插件,为了节省时间我全粘贴出来了,哈哈。
2.为了优雅性,除正方体外都只能显示水平方向的四个面而不要进行垂直方向上的转换;垂直方向上的变换只有三个状态:正常显示(0°),显示顶部(-90°),显示底部(90°),因为转换为180°时,会发现显示的东西都是倒立的(可以在z轴上变换180°来正常显示,但比较麻烦^)。所以在垂直方向上的平滑处理相对更简单点,可以先找到最近的正常显示的偏移量,再进行三个态的变换处理。当然,不管是垂直还是水平变换,都要先确保另一个方向上正常显示,才能保证变换后的显示是正常的。
3.为了保存便宜量,所以在插件中定义了全局变量。这样一来,为了确保友好性,一个页面上就只能对一个立方体进行旋转操作。怎样对不同的立方体保存对应的偏移量,请大家多多指教。