avalon js實現仿google plus圖片多張拖動排序

效果

google plus
拖動效果
拖動+響應式效果:http://v.youku.com/v_show/id_XMTM0MjQyMTI0OA==.htmljavascript

要求

  1. 兩邊對齊佈局,即圖片間間距一致,但左右兩邊的圖片與邊界的間距不必定等於圖片間間距,兼容ie7,8,firefox,chrome.css

  2. 瀏覽器尺寸變化,在大於必定尺寸時,每行自動增長或減小圖片,自動調整圖片間間距,以知足兩邊對齊佈局,這時每張圖片尺寸固定(這裏是200*200px);而小於必定尺寸時,每行圖片數量固定(這裏最小列數是3),這時圖片老是等比例拉伸或縮放。html

  3. 瀏覽器不一樣尺寸下,仍然能夠拖動排序。java

  4. 圖片,拖動代理裏的圖片始終保持等比例且水平垂直居中。git

  5. 拖動到相應位置時,位置左右的圖片發生必定偏移。若是在最左邊或最右邊,則只是該行的第一張圖片或最後一張圖片發生偏移。github

  6. 支持多張圖片拖動排序。web

實現

佈局及css

<div id='wrap'>
        <ul class='justify'>
            <li>
                <a href="javascript:;" class="no_selected"></a>
                <div class='photo_mask'></div>
                <div>
                    <div class="dummy"></div>
                    <p><img><i></i></p>
                </div>
            </li>
            <li class='justify_fix'></li>
        </ul>
    </div>

inline-block+flex-box+text-align:justify

這裏要兼容低版本瀏覽器,因此列表li佈局用的是inline-block.而兩邊對齊佈局
-低版本:inline-block+text-align:justify
-現代:inline-block+flex-box
具體參見本屌的模擬flexbox justify-content的space-between
這裏沒有用flex-box的align-content:space-around是由於沒法經過text-align:justify兼容低版本瀏覽器。
text-align:justify沒法讓最左邊,最右邊文字自動調整與box邊的間距。即便在外面box添加padidng,好比:chrome

li{
    margin:0 1%;
    ...
}
#wrap{
    padding:0 1%;
}

看起來好像是最左邊,最右邊與box邊界的間距和li之間的間距同樣,都是2%了。實際上,外面box設置的padding是永遠不會變的,而li之間的margin是它們之間間距的最小值。若是全部li之間的間距都是1%,這時,一行上仍然有多餘的空白,這些li會把空白均分了,這時它們之間的間距會大於1%.
具體的實現segmentfault

li{
    list-style-type: none;
    display:inline-block;
    *display: inline;
    zoom:1;
    max-width: 200px;
    max-height: 200px;
    width: 28%;
    border:1px solid red;
    position: relative;
    overflow: hidden;
    margin:10px 2%;
}
li[class='justify_fix']{
    border:none;
}
.justify {
    display: flex;
    align-items: flex-start;
    flex-flow: row wrap;
    justify-content: space-between;
    text-align: justify;
    text-justify: inter-ideograph;
    *zoom: 1; 
    -moz-text-align-last: justify;
    -webkit-text-align-last: justify;
    text-align-last: justify;
}
@media (-webkit-min-device-pixel-ratio:0) {
 .justify:after {
        content: "";
        display: inline-block;
        width: 100%;
    }
}

這裏要加上max-width,max-height.後面能夠看到單元格里面都是百分比,須要在外面限定最大尺寸。數組

圖片響應式+水平垂直居中

具體參見本屌的css圖片響應式+垂直水平居中
簡單說,就是

  • 添加一個「多餘」的div,padding-top: 100%,使得整個box響應式而且寬高比始終是1.

  • 若是不考慮ie7,直接圖片

img{
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    position:absolute;
    margin: auto;
    padding: auto;
}
  • 若是考慮ie7,

<p><img><i></i></p>

將上一點img樣式添加到這裏的p,而後

p{
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    position:absolute;
    margin: auto;
    padding: auto;
}
img{
    display: inline-block;
    *display: inline;
    zoom:1;
    vertical-align: middle;
}
i{
    display: inline-block;
    *display: inline;
    zoom:1;
    vertical-align: middle;
    height:100%;
}
  • 圖片響應式

img{
    max-width: 100%;
    max-height: 100%;
}

選中圖片

google plus是按住ctrl,點擊圖片,完成多選,這裏是點擊"方框"(這裏的<a class='no_selected'></a>)。
點擊後,把當前圖片的index傳給保存選中圖片index的數組(這裏的selected_index)。若是該index不存在,則添加;已存在,則刪除。而"方框"此時根據數組中是否存在該index調整樣式。

<div id='wrap' ms-controller='photo_sort'>
        <ul class='justify'>
            <li ms-repeat='photo_list'>
                <a href="javascript:;" class="no_selected" 
                ms-class-selected_icon='selected_photo.indexOf(el.src)>-1' 
                ms-click='select($index)'></a>
                ...
            </li>
            <li class='justify_fix'></li>
        </ul>
    </div>
var photo_sort=avalon.define({
    selected_index:[],//選中圖片的index列表,
    ...
    select:function(i){
        var selected_index=photo_sort.selected_index,
        selected_photo=photo_sort.selected_photo,//存儲選中圖片的名字(id)
        photo=photo_sort.photo_list[i].$model.src;//這裏以圖片的src爲標誌
        if(selected_photo.indexOf(photo)==-1){//選中圖片的index列表不存在,添加
            selected_index.ensure(i);
            selected_photo.ensure(photo);
        }else{
            selected_index.remove(i);
            selected_photo.remove(photo);
        }
    }
});

圖片的選中狀態必須用selected_photo.indexOf(photo)==-1判斷,最後會解釋爲何這樣作.

mousedown

這裏用了遮罩層,並在上面綁定mousedown事件。

<a href="javascript:;" class="no_selected" ms-class-selected_icon='selected_photo.indexOf(el.src)>-1' 
ms-click='select($index)'></a>
<div class='photo_mask' ms-mousedown='start_drag($event,$index)'></div>
...
var photo_sort=avalon.define({
            $id:'photo_sort',
            photo_list:[],//圖片列表
            selected_photo:[],//選中圖片的id列表
            selected_index:[],//選中圖片的index列表
            drag_flag:false,
            sort_array:[],//範圍列表,
            cell_size:0,//每一個單元格尺寸,這裏寬高比爲1
            target_index:-1,//最終目標位置的index
            col_num:0,//列數
            x_index:-1,//當前拖動位置的x方向index
            ...
        });
start_drag:function(e,index){
    if(photo_sort.selected_index.size()){//有選中的圖片
        photo_sort.target_index=index;//避免用戶沒有拖動圖片,但點擊了圖片,設置默認目標即當前點擊圖片
        photo_sort.cell_size=this.clientWidth;
        var xx=e.clientX-photo_sort.cell_size/2,yy=e.clientY-photo_sort.cell_size/2;//點下圖片,設置代理位置以點擊點爲中心
        $('drag_proxy').style.top=yy+avalon(window).scrollTop()+'px';
        $('drag_proxy').style.left=xx+'px';
        $('drag_proxy').style.width=photo_sort.cell_size+'px';
        $('drag_proxy').style.height=photo_sort.cell_size+'px';
        drag_proxy.select_num=photo_sort.selected_index.length;//設置代理中選擇圖片的數量
        if(drag_proxy.select_num>0){
            var drag_img=photo_sort.photo_list[photo_sort.selected_index[drag_proxy.select_num-1]];
            drag_proxy.src=drag_img.src;//將選中的圖片中最後一張做爲代理對象的"封面"
            photo_sort.drag_flag=true;
            $('drag_proxy').style.display='block';
        }
        //cell_gap:圖片間間距,first_gap:第一張圖片和外部div間間距
        var wrap_width=avalon($('wrap')).width(),wrap_offset=$('wrap').offsetLeft,first_left=$('wrap_photo0').offsetLeft,
        second_left=$('wrap_photo1').offsetLeft,first_gap=first_left-wrap_offset,cell_gap=second_left-first_left;
        photo_sort.col_num=Math.round((wrap_width-2*first_gap+(cell_gap-photo_sort.cell_size))/cell_gap);
        for(var i=0;i<photo_sort.col_num;i++)//把一行圖片裏的每張圖片中心座標x方向的值做爲分割點,添加到範圍列表
            photo_sort.sort_array.push(first_gap+cell_gap*i+photo_sort.cell_size/2);
        var target=this.parentNode;
        avalon.bind(document,'mouseup',function(e){
            onMouseUp(target);
        });
        if(isIE)
            target.setCapture();//讓ie下拖動順滑
        e.stopPropagation();
        e.preventDefault();
    }
}

鼠標點下,選中的圖片的遮罩出現,這裏是對其添加.photo_maskon

<div class='photo_mask' ms-class-photo_maskon='drag_flag&&selected_index.indexOf($index)>-1' 
ms-mousedown='start_drag($event,$index)'></div>

mousemove

drag_move:function(e){
    if(photo_sort.drag_flag){
        var xx=e.clientX,yy=e.clientY,offset=avalon($('wrap')).offset();
        var offsetX=xx-offset.left,offsetY=yy-offset.top;
        photo_sort.sort_array.push(offsetX);//把當前鼠標位置添加的範圍列表
        photo_sort.sort_array.sort(function(a,b){//對範圍列表排序
            return parseInt(a)-parseInt(b);//轉爲數值類型,不然會出現'1234'<'333'
        });
        //從已排序的範圍列表中找出當前鼠標位置的index,即目標位置水平方向的index
        var x_index=photo_sort.sort_array.indexOf(offsetX),y_index=Math.floor(offsetY/(photo_sort.cell_size+20)),
        size=photo_sort.photo_list.size();
        photo_sort.x_index=x_index;
        photo_sort.target_index=photo_sort.col_num*y_index+x_index;//目標在全部圖片中的index
        if(photo_sort.target_index>size)//目標位置越界
            photo_sort.target_index=size;
        photo_sort.sort_array.remove(offsetX);//移除當前位置
        $('drag_proxy').style.top=avalon(window).scrollTop()+yy-photo_sort.cell_size/2+'px';
        $('drag_proxy').style.left=xx-photo_sort.cell_size/2+'px';
    }
    e.stopPropagation();
}

幾點說明

  • 關於當前拖動到的位置斷定
    位置斷定

圖中每一個單元格的豎線,在水平方向把單元格分爲兩邊。每一個豎線把一行分爲5部分,判斷的時候,看鼠標當前的e.clientX在5個部分裏的哪一部分。

  • 這裏在判斷的時候用了排序。具體的,把每一個豎線的x座標和當前鼠標位置的x座標保存到數組(這裏的sort_array),排好序,而後indexOf看當前鼠標位置的x座標在數組中的位置,便可獲得當前拖動的目標位置。
    若是不用排序的話,代碼會像這樣

var target;
if(x>50+50){
    if(x>3*100+3*100+50+50){//最後一部分
        target=4;
    }else{
        target=(x-50-50)/(50+100+50);
    }
}else
    target=0;
  • 後面刪除當前鼠標位置的x座標,空出位置,留給下一次mousemove事件的x座標。

  • 關於當前拖動的目標位置左右的圖片發生必定偏移,無非就是對目標位置左右的圖片加上相應的class.

.prev{
    right: 40px;
}
.next{
    left: 40px;
}
<div id='wrap' ms-controller='photo_sort'>
        <ul class='justify' ms-mousemove='drag_move($event)'>
            <li ms-repeat='photo_list' ms-attr-id='wrap_photo{{$index}}' ms-class-prev='$index==target_index-1' 
            ms-class-next='$index==target_index'>
            ...
            </li>
            <li class='justify_fix'></li>
        </ul>
    </div>

這裏須要注意,當代理拖動到最左邊或最右邊時,因爲佈局是inline-block,此時目標位置所在行的上一行(若是有)的最後一個單元格或下一行(若是有)的第一個單元格也會發生偏移。
圖片描述
解決方法是設置變量x_index,表示單元格在x方向的index.在添加偏移class的時候,增長斷定條件。

<li ms-repeat='photo_list' ms-attr-id='wrap_photo{{$index}}' ms-class-prev='$index==target_index-1&&x_index>0' 
ms-class-next='$index==target_index&&x_index<col_num'>
...
</li>

mouseup

function onMouseUp(target){
            if(photo_sort.drag_flag){
                for(var i=0,len=photo_sort.selected_index.size();i<len;i++){//遍歷選中圖片
                    var item_index=photo_sort.selected_index[i],data=photo_sort.photo_list,
                    target_index=photo_sort.target_index,temp;
                    if(item_index<target_index){//目標位置在選中圖片以後
                        temp=data[item_index].src;
                        for(var j=item_index;j<target_index;j++)
                            data[j].src=data[j+1].src;
                        data[target_index-1].src=temp;
                    }else{//目標位置在選中圖片以前
                        temp=data[item_index].src;
                        for(var j=item_index;j>target_index;j--)
                            data[j].src=data[j-1].src;
                        data[target_index].src=temp;
                    }
                }
                photo_sort.photo_list=data;//更新數據
                photo_sort.target_index=-1;//各類重置,初始化
                photo_sort.sort_array=[];
                photo_sort.col_num=0;
                photo_sort.x_index=-1;
                photo_sort.selected_photo.clear();
                photo_sort.selected_index.clear();
                $('drag_proxy').style.display='none';
                photo_sort.drag_flag=false;
                avalon.unbind(document,'mouseup');
                if(isIE)
                    target.releaseCapture();
            }
        }

這裏主要就是對圖片列表的重排。

  • 目標位置在選中圖片以前
    圖片描述

先把原始圖片保存在temp,而後把從目標位置圖片到原始圖片前一位置的圖片,依次後移一個位置,最後把temp放到目標位置。

  • 目標位置在選中圖片以後
    圖片描述

和上面差很少,只不過這裏是把從目標位置圖片到原始圖片後一位置的圖片,依次前移一個位置。

說明

  • 不能像data[j]=data[j+1]這樣賦值,進而更新視圖。由於avalon不支持單個轉換,若是想更新,須要將整個子VM從新賦以一個新的對象。也就是photo_sort.photo_list=sortedData從新賦值,更新視圖。

  • 前面判斷圖片選中狀態爲何用selected_photo.indexOf(photo),而不是selected_index.indexOf(i),是由於更新視圖後,avalon不能自動更新當前圖片的index,也就是說若是圖片一出來就是第一張,那它的index就永遠是0,不會跟着它的位置改變。

移動順序問題

重現

這裏爲了方便查看順序,稍微修改了下html.
1
能夠看到,拖動第2,3張圖片到第8,9張圖片之間,結果應該是第8張圖片在第二行最左邊,而後向右依次是第2,3張圖片,最後是第9張圖片.而這裏顯然不是想要的結果。

根源

出現這個問題的緣由在於
依次移動目標圖片到目標位置,對多張目標圖片,會有一個移動前後的考量。具體的,
2
假設上圖要移動的目標圖片是第2,3張圖片,先移動第2張圖片到目標位置,這時第3張圖片會在第2張原來的位置上,這時成爲第2張。可是前面,

function onMouseUp(target){
            if(photo_sort.drag_flag){
                for(var i=0,len=photo_sort.selected_index.size();i<len;i++){//遍歷選中圖片
                    ...
                }
                ...
            }
        }

仍是在依次遍歷目標圖片,selected_index=[2,3];,下一個會遍歷如今的第3張圖片.

解決

解決方法很容易想到,就是先移動第3張圖片,後移動第2張圖片.
上面的例子是目標位置在選中圖片以後,固然目標位置在選中圖片以前也存在這個問題。
具體到代碼上

function onMouseUp(target){
    if(photo_sort.drag_flag){
        photo_sort.selected_index.sort(function(a,b){//對範圍列表排序
            return parseInt(a)-parseInt(b);
        });
        var size=photo_sort.selected_index.size();
        var data=photo_sort.photo_list,target_index=photo_sort.target_index,
        pos_arr=photo_sort.selected_index.$model,
        result=data.slice(0,data.size());
        pos_arr.push(target_index);//pos_arr存放選中的目標圖片+目標位置,並排好序
        pos_arr.sort(function(a,b){//對範圍列表排序
            return parseInt(a)-parseInt(b);
        });
        var target_pos=pos_arr.indexOf(target_index),temp;
        //目標位置在選中圖片以後,從目標位置開始,依次向前遍歷目標圖片
        for(var i=target_pos-1;i>=0;i--){
            var item_index=pos_arr[i];
            temp=data[item_index].$model;
            for(var j=item_index;j<target_index;j++){
                data[j].$model=data[j+1].$model;
            }
            data[target_index-1].$model=temp;
        }
        //目標位置在選中圖片以前,從目標位置開始,依次向後遍歷目標圖片
        for(var i=target_pos+1;i<pos_arr.length;i++){
            var item_index=pos_arr[i];
            temp=data[item_index].$model;
            for(var j=item_index;j>target_index;j--)
                data[j].$model=data[j-1].$model;
            data[target_index].$model=temp;
        }
        photo_sort.photo_list=data;//更新數據
        ...
    }
}

實際上就是以目標位置爲中心,向左右兩邊遍歷選中圖片。具體的
圖片描述
選中圖片是第1,2,8,9張圖片,目標位置是4.這時pos_arr=[1,2,4,8,9];,而後先遍歷4以前的選中圖片,2->1,而後是4以後的選中圖片,8->9.這樣就避免了移動順序問題。

後記

事實上,google plus在細節上還作了

  • 框選圖片

  • 若是有滾動條,且拖動位置快要超出當前界面,滾動條會自動上移或下移。
    這兩個本屌就不作了,原理也是很簡單的。


下載

相關文章
相關標籤/搜索