首页 > 代码库 > 前端心得---仿IOS拾取器控件(转轮控件)

前端心得---仿IOS拾取器控件(转轮控件)

    希望做一个类似IOS拾取器的控件,在IOS上该控件的效果是这样的:,我也把该效果称之为为轮子效果。

 

  要实现这个效果,能够用到的技术点非常简单,无非是transform的translate3d和rotate,不过要想很好的实现,还要建立一个精确的数学模型,来解决如何【摆放】的问题。特别是这个效果不是静态的,需要满足鼠标滑动的时,这个轮子要转起来,这就需要仔细思索了。当然,在最开始重点还是要搞清楚自变量是什么、因变量是什么、它们之间的关系是什么以及该需求的一些性质。找到了好的性质,可以减轻工作量,并且让代码可以更加简洁。

 

  做这个效果,首先想到的,就是用div表示每个小条目,然后想象成这些div是贴在一个圆上面,滚动这个圆。先用html把结构展示一下:

 1 <div class="border"> 2     <div class="item">0</div> 3     <div class="item">1</div> 4     <div class="item">2</div> 5     <div class="item">3</div> 6     <div class="item">4</div> 7     <div class="item">5</div> 8     <div class="item">6</div> 9     <div class="item">7</div>10     <div class="item">8</div>11     <div class="item">9</div>12     <div class="item">10</div>13     <div class="item">11</div>14     <div class="item">12</div>15     <div class="item">13</div>16     <div class="item">14</div>17     <div class="item">15</div>18     <div class="item">16</div>19     <div class="item">17</div>20     <div class="item">18</div>21 </div>

 

 

  接着把其中的css代码也写出来:

 1 <style> 2 * { 3     -webkit-user-select: none; 4 } 5  6 .item { 7     height: 30px; 8     line-height: 30px; 9     font-size: 15px;10     font-weight: bolder;11     color: #222;12     width: 400px;13     background: white;14     text-align: center;15     position: absolute;16     top: 50%;17     margin-top: -15px;18 }19 20 .border {21     overflow: hidden;22     cursor: pointer;23     position: relative;24     height: 240px;25     width: 400px;26     border: 2px solid black;27 }28 29 .hide {30     display: none;31 }32 </style>

 

  注意上面的border采取的是相对(relative)定位,里面的条目(item)采用的是绝对定位,而且是垂直居中,这样的话,就会有如下的效果:

                

  由于给item设定了背景色,所以只会看到最后一个item。接下来我们要做的工作是,把这19个item先摆成像是贴在圆上一样。这需要先挖掘一下这个需求的性质,因为性质会成为我们的突破口。

 

  想要摆出这个效果,需要先进行分析。我们知道,由于每个item的高度相同,所以每两个相邻的item之间的旋转角度之差相同,而这个角度之差可以通过公式2 * Math.asin( height / 2 / r )计算出来。另外,转轮的旋转角度和item的旋转角度相同,那么我们需要知道已经旋转的角度,用这个角度以及角度之差我们可以知道任何一个item的旋转角度,然后再根据这个角度计算出item摆放的位置。

 

  有了上述的分析,想要摆放出一个很漂亮的拾取器控件样式就不那么难了。不过在此之前,要做一下简单的准备。

  

  首先,需要一个设定css样式的javascript函数,因为在摆放的过程中,要频繁用到javascript来设定css的transform样式,因此极有必要将该操作提取出来写成为一个函数,不妨将其命名为css,这部分的代码如下:

1 function css( el, style ) {2     loopObj( style, function ( name, value ) {3         el.style.setProperty( name, value, "");4     } )5 }

  代码非常简单,但是非常实用。其中setProperty方法的第三个参数可以为important或者是一个空字符串,这里设成空字符串。

 

  接下来进行布局。由于部署在球面上的item具有对称性,所以对19个item,从第10个开始布局,然后循环10次,从中间到两边依次对item进行布局。这部分代码如下所示:

 1 var r = 90; 2 var height = 30; 3 var dAngle = 2 * Math.asin( height / 2 / r ); 4 var first = items.length / 2 << 0; 5  6 function setPosition( curAngle ) { 7     d.loop( first + 1, function ( i ) { 8         doSet( items[first + i], curAngle - i * dAngle ); 9         doSet( items[first - i], curAngle + i * dAngle );10     } );11 }

 

 

  其中doSet的代码如下所示:

1 function doSet( el, angle ) {2     Math.abs( angle ) > Math.PI / 2 ? el.classList.add( "hide" ) : el.classList.remove( "hide" );3     d.css( el, {4         "-webkit-transform" : "translate3d(0," + (r * Math.sin( -angle )) + "px,0) rotateX(" + 180 * angle / Math.PI + "deg)"5     } );6 }

 

 

  添加初始化的javascript代码:setPosition( 0 ),然后在浏览器中打开后就能看到这样的效果,可以看出布局成功了,正是我们想要的效果。

   

   接下来的工作中,要让它转动起来,由于上面考虑的非常充分,已经不必再引入新的变量,只要记录鼠标滑动的距离,得出相应的curAngle,然后传给setPosition即可。相关代码如下所示:

 1 var end = d.Events(); 2  3 d.dragY( border, border, { 4     moving : function () { 5         var moving = d.Events(); 6         moving.regist( function ( e ) { 7             setPosition( -e.dy / 50 ); 8         } ); 9         return moving;10     },11     end : end,12     setMove : function ( e ) {13         console.log( e.dy );14     }15 } );

 

   在浏览器中打开,然后就可以看到已经可以转动了。

  

  但是这个目前仍然美中不足,就是对旋转的角度没有记录,导致第二次转动时,还是从新开始,所以还需要改动一下:

 1 var alreadyS = 0; 2  3 d.dragY( border, border, { 4     moving : function () { 5         var moving = d.Events(); 6         moving.regist( function ( e ) { 7             console.log( alreadyS - e.dy ); 8             setPosition( (alreadyS - e.dy) / 50 ); 9         } );10         return moving;11     },12     end : function () {13         var end = d.Events();14         end.regist( function ( e ) {15             alreadyS = alreadyS - e.dy;16         } );17         return end;18     },19     setMove : function ( e ) {20     }21 } );

  这里添加了一个全局变量alreadyS,记录滑动的距离,这样就不用每次滑动都要重新开始了。更新alreadyS的时机在每次滚动结束结束的时候,所以新添加了end事件。