Vue - 組件開發和實例旋轉輪盤

使用 vue 編寫的一個可旋轉組件,如圖

vue轉盤

demo地址: https://github.com/tanf1995/m...vue

佈局

  • 用卡片數量均分360度圓,使用絕對定位分佈在外部容器上,自身經過rotate旋轉react

    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
           }
       },
  • 外部容器定位於瀏覽器窗口之下,露出正上方部分出來git

    <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),疊加一次整體滑動的距離github

    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(){dom

    let time = 10;
       let timer = setInterval(() => {
           this.xGap += this.direction*3;
           if(--time <= 0) clearInterval(timer);
       }, 20)

    },函數

請教

  • 原本設想的是經過prop來傳遞卡片的內部結構和數組數據,例如傳遞一個渲染函數,經過react能夠輕鬆的實現,可是vue這招行不通。請問如何可以作到這點呢?react僞代碼以下佈局

    <Component
        renderItem={item => <Child propName={item.props} data={item.data} />}
    >
    </Component>
相關文章
相關標籤/搜索