Vue interesting turntable assembly

Keywords: Javascript Vue React github

A rotatable component written with vue, as shown in the figure

  • layout

    • Use the number of cards to divide the 360 degree circle evenly, use the absolute positioning to distribute on the external container, and rotate itself through the 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
           }
       },
       
    • The external container is positioned under the browser window to expose the upper part

       <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>
       
  • Rotation of the disk

    • onmousedown and onmouseup are used to judge whether the mouse is in the down state and clear the last dragged data
    • The rotation of the disc updates the angle with the total distance of lateral sliding
    • According to how the disk rotates, the design is to stack the overall sliding distance once at each small interval (such as 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));
      }
      
  • Smooth rotation

    If there is no sliding inertia, after sliding, no matter how fast the speed is, the turntable stops immediately after releasing the mouse, making the effect very stiff. So after the completion of sliding, use the sliding speed at the last moment to make the rotary table move in uniform deceleration until the speed is 0, and when the speed is 0, in the design of slow and small uniform sliding, the final effect is relatively smooth.
    
    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 address: https://github.com/tanf1995/m...
  • Consult

    The original idea is to pass the internal structure and array data of the card through prop, for example, to pass a rendering function, which can be easily realized through react, but vue is not feasible. How can we do this? The react pseudo code is as follows

       <Component
           renderItem={item => <Child propName={item.props} data={item.data} />}
       >
       </Component>
    

Posted by richardw on Sun, 03 Nov 2019 16:04:25 -0800