Vue有趣的可旋转转盘组件

6次阅读

共计 4070 个字符,预计需要花费 11 分钟才能阅读完成。

使用 vue 编写的一个可旋转组件,如图

  • 布局

    • 用卡片数量均分 360 度圆,使用绝对定位分布在外部容器上,自身通过 rotate 旋转

       
           computedCardPosStyle(index){
           let deg = index * this.unitCardDeg;
           let absDeg = Math.abs((deg + this.turnRotate) % 360);
           let z_index = absDeg > 180 ? Math.ceil(absDeg-180): Math.ceil(180-absDeg);
      
           return {
               width: this.cardWidth + "px",
               height: this.cardHeight + "px",
               top: -Math.cos(deg*Math.PI/180)*this.turntableR + "px",
               left: Math.sin(deg*Math.PI/180)*this.turntableR + "px",
               transform: `translate(-50%, -50%) rotate(${deg}deg)`,
               zIndex: z_index
           }
       },
       
    • 外部容器定位于浏览器窗口之下,露出正上方部分出来

       <div 
           class="container"
           :style="{width: `${turntableR*2 + cardWidth}px`,
               height: `${turntableR*2 + cardHeight}px`,
               left: `${screenWidth/2 - turntableR-cardWidth/2}px`,
               top: `${outerWrap? -(1-wrapScale)*(turntableR+cardHeight/2) + screenHeight - wrapScale*(cardHeight + bottomPos + cardHeight*reletiveTop): 
                   -(1-wrapScale)*(turntableR+cardHeight/2)
               }px`,
               transform: `scale(${wrapScale})`,
           }"ref="container"
       >
        ...
      </div>
       
  • 圆盘的转动

    • onmousedown、onmouseup 用来判断鼠标是否处于按下状态,并且清空上一次拖动的数据
    • 圆盘的转动以横向滑动的总距离更新角度
    • 针对圆盘如何转动,设计按每一个小的间隔时间(如 20ms),叠加一次总体滑动的距离

      handleMouseDown(e){e.preventDefault();
          clearInterval(this.UDLMactionTimer);
          this.mouseIsDown = true;
          this.startX = e.clientX || e.touches[0].clientX;
          this.endX = e.clientX || e.touches[0].clientX;
      },
      handleMouseUp(e){e.preventDefault();
          this.mouseIsDown = false;
          clearInterval(this.timer);
          clearInterval(this.UDLMactionTimer);
          this.timer = null;
          this.startX = 0;
          this.endX = 0;
          if(this.lastSpeed) this.UDLMaction();},
      handleMouseMove(e){e.preventDefault();
          this.endX = e.clientX || e.touches[0].clientX;
          if(!this.mouseIsDown) return;
          if(!this.timer){this.timer = setInterval(() => {
                  let moveGap = this.endX - this.startX;
      
                  this.lastSpeed = moveGap/this.timeGap;
                  this.xGap += moveGap;
                  this.direction = moveGap > 0 ? 1 : -1;
                  this.startX = this.endX;
              }, this.timeGap);
          }
      },
      
      mounted(){
      let container_dom = this.outerWrap ? this.$refs.outerWrap : this.$refs.container;
      
      container_dom.addEventListener('mousedown', this.handleMouseDown.bind(this));
      container_dom.addEventListener('mouseup', this.handleMouseUp.bind(this));
      container_dom.addEventListener('mouseleave', this.handleMouseUp.bind(this));
      container_dom.addEventListener('mousemove', this.handleMouseMove.bind(this));
      container_dom.addEventListener('touchstart', this.handleMouseDown.bind(this));
      container_dom.addEventListener('touchend', this.handleMouseUp.bind(this));
      container_dom.addEventListener('touchcancel', this.handleMouseUp.bind(this));
      container_dom.addEventListener('touchmove', this.handleMouseMove.bind(this));
      
      window.addEventListener('resize', this.responseContainerScale.bind(this));
      window.addEventListener('load', this.responseContainerScale.bind(this));
      },
      beforeDestroy(){
          let container_dom = this.outerWrap ? this.$refs.outerWrap : this.$refs.container;
      
          container_dom.removeEventListener('mousedown', this.handleMouseDown.bind(this));
          container_dom.removeEventListener('mouseup', this.handleMouseUp.bind(this));
          container_dom.removeEventListener('mouseleave', this.handleMouseUp.bind(this));
          container_dom.removeEventListener('mousemove', this.handleMouseMove.bind(this));
          container_dom.removeEventListener('touchstart', this.handleMouseDown.bind(this));
          container_dom.removeEventListener('touchend', this.handleMouseUp.bind(this));
          container_dom.removeEventListener('touchcancel', this.handleMouseUp.bind(this));
          container_dom.removeEventListener('touchmove', this.handleMouseMove.bind(this));
      
          window.removeEventListener('resize', this.responseContainerScale.bind(this));
      }
      
  • 旋转效果平滑

     如果没有滑动惯性,当滑动完之后,无论滑动的时候速度如何的快,在松开鼠标后转盘立刻停下,使得效果非常生硬。所以在滑动完成之后,利用最后时刻的滑动速度,让转盘做匀减速运动直至速度为 0,并且在速度为 0 时,在设计缓慢细小的匀速滑动,最后呈现出来的效果就比较平滑了。UDLMaction(){
           let a = -this.reduceSpeed*this.direction;
           this.UDLMactionTimer = setInterval(() => {this.lastSpeed = (this.lastSpeed + a)*this.direction >= 0? this.lastSpeed + a: 0;
               this.xGap += (this.lastSpeed) * this.timeGap;
               if(!this.lastSpeed){this.moreDynamic();
                   return clearInterval(this.UDLMactionTimer);
               }
           }, this.timeGap);
       },
       moreDynamic(){
           let time = 10;
           let timer = setInterval(() => {
               this.xGap += this.direction*3;
               if(--time <= 0) clearInterval(timer);
           }, 20)
       },
       
    
  • demo 地址:https://github.com/tanf1995/m…
  • 请教

    本来设想的是通过 prop 来传递卡片的内部结构和数组数据,例如传递一个渲染函数,通过 react 可以轻松的实现, 但是 vue 这招行不通。请问如何能够做到这点呢?react 伪代码如下

       <Component
           renderItem={item => <Child propName={item.props} data={item.data} />}
       >
       </Component>
    
正文完
 0