js 實現多重羅盤轉動

引子

這幾天一直在忙一個可滑動的轉盤的demo,網上也有相似的例子,可是根據老闆的需求來改他們的代碼,還不如從新寫個徹底符合需求的插件。
想法很美好,可是新手上路...css

效果連接文末git

需求

image
image

這個demo給的很是簡單,能轉動的地方有三處,內盤、外盤和指針,這三個上的集合的交集產生一個連接,經過中間的按鈕跳轉。github

這個需求乍一看老簡單老簡單的,可是做爲一個菜雞第一次上道,堪比開碰碰車,頭破血流。算法

分析

在作以前,也是根據本身的理解來寫的旋轉角度問題:chrome

  • 轉盤轉動的作法是:設定圓心爲轉動原點,動態的修改旋轉角度;
  • 在touchmove 計算兩點與中心點的角度。

在旋轉上大致上須要明白的也就這兩點,可是在實際計算角度上卻有不少問題。bash

彎道1之計算角度

計算角度首先要用到的一個數學方法就是反函數,在JS中表示反函數的方法有兩個:ide

  • Math.atan
  • Math.atan2

說實話它們兩個的區別對於本次demo真沒有測出什麼差別來,可是相比 atan在y特別大的時候會有偏差產生的狀況下,果斷選擇了atan2函數

(function($){
        $.fn.CompassRotate=function(options){
            var defaults={
                trigger:document,           
                centerX:0,                     
                centerY:0,                      
                debug:false
            },_this=this;
            var ops=$.extend(defaults,options);
            function Init(){
                //初始化圓心點
                if(ops.centerX==0 && ops.centerY==0){
                    ops.centerX=_this.offset().left+_this.width()/2;
                    ops.centerY=_this.offset().top+_this.height()/2
                }
                $(ops.trigger).on("touchstart",function(event){
                    $(document).on("touchmove",movehandle);
                });
                $(ops.trigger).on("touchend",function(event) {
                    $(document).unbind("touchmove");
                });
            }
            //鼠標移動時處理事件
            function movehandle(event){
                var touch = event.originalEvent.targetTouches[0];
                var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);

                if(ops.debug) console.log(ops.centerX+"-"+ops.centerY+"|"+touch.pageX+"-"+touch.pageY+" "+dis);

                rotate(dis);
            }
            //計算兩點的線在頁面中的角度
            function angle(centerx, centery, endx, endy) {
                var diff_x = endx - centerx,
                    diff_y = endy - centery;
                var c=360 * Math.atan2(diff_y , diff_x) / (2 * Math.PI);
                c=c<=0?(360+c):c;

                return c; 
            }
            //設置角度
            function rotate(angle,step){
                $(_this).css("transform", "translate3d(-50%,-50%,0) rotateZ(" + angle + "deg)");
            }
            // 指針指向角度變化和生成url
            function angleOrLink(angle) {
                Angle = angle;
            }
            Init();
        };
    })(jQuery);
    $(".box").CompassRotate({trigger:$(".box"),debug:true});複製代碼

囉裏囉嗦不如直接貼上代碼,你們看得更明白些。ui

彎道2之區域集合變化

作過轉盤抽獎的大佬都知道,每一個獎品都對應一個角度集合,指針所轉的角度[0,360]看看對應落在哪一個集合上,而這個轉盤也是同理,可是惟一不一樣的地方在於,內盤和外盤的集合是可變化的,並非固定不變的。this

var insideCollection = [
    {
        /* GC+S1 */
        min: 270,
        max: 360,
        reverse: false,
        mark: 's1gc'
    },
    {
        /* BC+AT */
        min: 0,
        max: 45,
        reverse: false,
        mark: 'bcat'
    },
    {
        /* BC+GT */
        min: 45,
        max: 90,
        reverse: false,
        index: 'bcgt'
    },
    {
        /* mCRC+FOLFOX */
        min: 90,
        max: 180,
        reverse: false,
        mark: 'mCRC'
    },
    {
        /* eCRC+化療 */
        min: 225,
        max: 270,
        reverse: false,
        mark: 'eCRC1'
    },
    {
        /* eCRC+FOLFOX */
        min: 180,
        max: 225,
        reverse: false,
        mark: 'eCRC2'
    }

];
var outsideCollection = [
    {
        /* 研究 */
        min: 270,
        max: 342,
        reverse: false,
        mark: '研究'
    },
    {
        /* 指南 */
        min: 342,
        max: 54,
        reverse: true,
        mark: '指南'
    },
    {
        /* 競品 */
        min: 54,
        max: 126,
        reverse: false,
        mark: '競品'

    },
    {
        /* 資料 */
        min: 126,
        max: 198,
        reverse: false,
        mark: '資料'
    },
    {
        /* 機制 */
        min: 198,
        max: 270,
        reverse: false,
        mark: '機制'
    }

];複製代碼

min,max不用說了,就是表示集合,reverse 這個屬性表明的是什麼呢?
在作區間劃分的時候,角度的變化永遠都是0-360°,「0==360」。因此,當某個集合的區間是[340,25]的時候該怎麼表示呢?
固然,每次轉動都有且只有一個集合會面臨這樣的狀況,因此我用一個屬性來表示這個區間跨角度了。

// 轉盤區間分佈變化
function collectionChange(angle,array) {
    array.forEach(function (ele,index) {
        ele.reverse = false;
    });
    array.forEach(function (ele,index) {
        ele.min = (Number(angle)+Number(ele.min))%360;
        ele.max = (Number(angle)+Number(ele.max))%360;
        if(ele.min > ele.max){
            ele.reverse = true;
        }
    });
    console.log(array)
}複製代碼

mark 也不用多談,選中了集合該表示表示了呀。

代碼貼到這也基本完成了大致功能,最後也是在點擊連接的時候根據內外盤的 mark 來匹配連接了:

$('#compass_5').on('click',function(){
    var angle = Angle;
    // 內盤標號
    var link = contrast(insideCollection) + contrast(outsideCollection);
    console.log(link);
    function contrast(array){
        var link ;
        array.forEach(function (ele,index) {
            if(angle >= ele.min%360 && angle <= (ele.max%360 ==0?360:ele.max%360)){
                link = ele.mark;
            }
            else if(ele.reverse){
                if(angle<=360 && angle >=270){
                    if(angle >= ele.min%360 && angle <= (ele.max%360 ==0?360:ele.max%360+360)){
                        link = ele.mark;
                    }
                }
                else if(angle>=0&&angle<=90){
                    if(angle+360 >= ele.min%360 && angle+360 <= (ele.max%360 ==0?360:ele.max%360+360)){
                        link = ele.mark;
                    }
                }
            }
        });
        return link;
    }
})複製代碼

彎道3之坑王之王

上面說到功能大致完成了,那只是循序漸進的在輪盤上只選擇一個點進行轉動,若是在不一樣位置屢次轉動,發現整個轉盤癱瘓了——mark對應不上了。

作這個demo第一步,我是從一個簡單的指針轉盤開始起手的,也就是完成一個轉動指針的基本操做,因此整套流程下來是可行的,由於這個指針訂好了轉動圓心,它的可選區域僅僅是辣麼一小塊,因此根本看不到爲後面埋了多大坑。

反函數計算角度問題

var c=360 * Math.atan2(diff_y , diff_x) / (2 * Math.PI); 
// c [-180,180];
c=c<=0?(360+c):c;
// c [0,360];複製代碼

這樣計算角度對於指針來講,沒什麼問題,可是對於轉盤上來講可能就是個噩夢。

由於它的着落點並不肯定。

致使當你點到不一樣區域的時候,它會給你直接將轉動的角度賦值,因此會造永遠是中間那條線跟着手指滑動。

image
image

坑王之王連接(chrome調試器裏查看)

這樣的操做遇到的坑就是起始位置隨着手指的變更會致使各個區域的區間也應該發生相應的變化,因此在 touchstart 還要進行一步操做,計算上一次結束位置與目前位置的夾角,而後再次更改區間變化。

$(ops.trigger).on("touchstart",function(event){
    var touch = event.originalEvent.changedTouches[0];
    var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);
    startAngle = dis;
    //再次滑動轉盤後的角度與上一次結束角度不一致的狀況(內盤)
    if(startAngle != ops.initAngle_in && ops.initAngle_in != 0){
        if(ops.initAngle_in>startAngle){
            insideDishAngleChangeSecondary((Number(startAngle+360)-ops.initAngle_in));
        }
        else if(ops.initAngle_in< startAngle){
            insideDishAngleChangeSecondary((startAngle-ops.initAngle_in));
        }

    }
    $(document).on("touchmove",movehandle);
});複製代碼

修改後的羅盤

上一版的羅盤基本操做是將錯就錯,產生了一系列bug,雖然都克服了一系列bug,但仍是都是在挖坑,只不過坑是平行挪動,這個坑挖不動了換了個方向繼續挖而已。

岔路

從新審視本身的思路時,才發現本身是多麼的蠢。

以前的算法是手指指在哪裏,開始點爲0,結束點爲所指點 ,在 touchmove 給羅盤賦角度值時,直接將兩點造成的角度賦給了羅盤 rotate(angle)。以後的一系列操做都是爲這個地方買單,不管是從新寫個函數記錄變換角度在 touchmove 開始以前賦給羅盤分佈區間、仍是中心點僵硬隨着手指轉動。

從新思考了下羅盤的轉法,有了以前的鋪墊,因此思路也變得特別清晰了。
實現這個需求,記錄的數據一共有三個:

  • actual_angle :開始點和結束點與中心點的夾角,這就是羅盤每次轉動的度數,該值須要累加;
  • addAngle :每次轉動結束後,須要給羅盤分佈區間增長的值,該值等同於 actual_angle
  • startAngletouchstart 時手指着落點,即開始點。
$.fn.RotateH=function(options){
    var defaults={
        trigger:document,           
        centerX:0,                      
        centerY:0,                      
        debug:false
    },_this=this;
    var ops=$.extend(defaults,options);
    var startAngle,addAngle,
        actual_angle = 0;
    //初始化
    function Init(){
        //初始化圓心點
        if(ops.centerX==0 && ops.centerY==0){
            ops.centerX=_this.offset().left+_this.width()/2;
            ops.centerY=_this.offset().top+_this.height()/2
        }
        $(ops.trigger).on("touchstart",function(event){
            var touch = event.originalEvent.changedTouches[0];
            var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);
            startAngle = dis;
            $(document).on("touchmove",movehandle);
        });
        $(ops.trigger).on("touchend",function(event) {

            var touch = event.originalEvent.changedTouches[0];
            var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);

            //每次轉動的角度
            if(dis >=startAngle){
                //羅盤累加轉動度數
                actual_angle += (dis-startAngle);
                //區間每次增長度數
                addAngle = (dis-startAngle);
            }
            else if(dis <startAngle){
                actual_angle += (dis+360-startAngle);
                addAngle = (dis+360-startAngle)
            }
            if(ops.collection) collectionChange(addAngle,ops.collection);
            else angleOrLink(dis);
            $(document).unbind("touchmove");
        });
    }
    //鼠標移動時處理事件
    function movehandle(event){

        // 獲取兩點之間角度
        var touch = event.originalEvent.targetTouches[0];
        var dis = angle(ops.centerX,ops.centerY,touch.pageX, touch.pageY);
        var Angle = 0;

        if(ops.debug) console.log(ops.centerX+"-"+ops.centerY+"|"+touch.pageX+"-"+touch.pageY+" "+dis);

        if(ops.pointer){
            rotate(dis);
        }
        else {
            //每次轉動的角度
            if(ops.debug) {
                console.log("——————————————————————");
                console.log('上次轉動的角度:'+actual_angle);
            }
            if(dis >=startAngle){
                Angle = dis-startAngle;
                if(ops.debug) {
                    console.log("轉動角度:"+Angle);
                    console.log("實際轉動角度:"+(Angle+actual_angle));
                }
                rotate((Angle+actual_angle));
            }
            else if(dis <startAngle){
                Angle = dis-startAngle+360;
                if(ops.debug){
                    console.log("轉動角度:" + Angle);
                    console.log("實際轉動角度:"+(Angle+actual_angle));
                }
                rotate((Angle+actual_angle));
            }
        }
    }
    //計算兩點的線在頁面中的角度
    function angle(centerx, centery, endx, endy) {
        var diff_x = endx - centerx,
            diff_y = endy - centery;
        var c=360 * Math.atan2(diff_y , diff_x) / (2 * Math.PI);
        c=c<=0?(360+c):c;

        return c;
    }
    //設置角度
    function rotate(angle,step){
        $(_this).css("transform", "translate3d(-50%,-50%,0) rotateZ(" + angle + "deg)");
    }
    // 轉盤區間分佈變化
    function collectionChange(angle,array) {
        array.forEach(function (ele,index) {
            ele.reverse = false;
        });
        array.forEach(function (ele,index) {
            ele.min = (Number(angle)+Number(ele.min))%360;
            ele.max = (Number(angle)+Number(ele.max))%360;
            if(ele.min > ele.max){
                ele.reverse = true;
            }
        });
        if(ops.debug) console.log(array);
    }
    // 指針所轉角度
    function angleOrLink(angle) {
        Angle = angle;
    }
    Init();
};複製代碼

效果連接地址:perfectCompass.github.io (這是個ipad demo,請在chrome調試器查看)

github 地址:github.com/suiyang1714…

總結

這個demo最終是本身靠時間磨出來了的,沒有特別高的技術含量,主要是在這個過程當中思考。若是一開始想明白了每一步要幹什麼,也不會拐那麼多的彎道了。我一開始的想法是,羅盤先能轉動,而後再考慮的區間變化,出現問題解決解決問題,沒有看到爲何會出現這個問題。基本是走一步看一步。心好累。

相關文章
相關標籤/搜索