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

时间:2023-02-01 17:12:12

    希望做一个类似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)采用的是绝对定位,而且是垂直居中,这样的话,就会有如下的效果:

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

  由于给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 ),然后在浏览器中打开后就能看到这样的效果,可以看出布局成功了,正是我们想要的效果。

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

   接下来的工作中,要让它转动起来,由于上面考虑的非常充分,已经不必再引入新的变量,只要记录鼠标滑动的距离,得出相应的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事件。