资讯专栏INFORMATION COLUMN

avalon js实现仿google plus图片多张拖动排序

马龙驹 / 2386人阅读

摘要:支持多张图片拖动排序。解决方法是设置变量表示单元格在方向的在添加偏移的时候,增加判定条件。根源出现这个问题的原因在于依次移动目标图片到目标位置,对多张目标图片,会有一个移动先后的考量。

效果



拖动+响应式效果:http://v.youku.com/v_show/id_XMTM0MjQyMTI0OA==.html

要求

两边对齐布局,即图片间间距一致,但左右两边的图片与边界的间距不一定等于图片间间距,兼容ie7,8,firefox,chrome.

浏览器尺寸变化,在大于一定尺寸时,每行自动增加或减少图片,自动调整图片间间距,以满足两边对齐布局,这时每张图片尺寸固定(这里是200*200px);而小于一定尺寸时,每行图片数量固定(这里最小列数是3),这时图片总是等比例拉伸或缩放。

浏览器不同尺寸下,仍然可以拖动排序。

图片,拖动代理里的图片始终保持等比例且水平垂直居中。

拖动到相应位置时,位置左右的图片发生一定偏移。如果在最左边或最右边,则只是该行的第一张图片或最后一张图片发生偏移。

支持多张图片拖动排序。

实现 布局及css
    
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,比如:

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

看起来好像是最左边,最右边与box边界的间距和li之间的间距一样,都是2%了。实际上,外面box设置的padding是永远不会变的,而li之间的margin是它们之间间距的最小值。如果所有li之间的间距都是1%,这时,一行上仍然有多余的空白,这些li会把空白均分了,这时它们之间的间距会大于1%.
具体的实现

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,

将上一点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,点击图片,完成多选,这里是点击"方框"(这里的)。
点击后,把当前图片的index传给保存选中图片index的数组(这里的selected_index)。如果该index不存在,则添加;已存在,则删除。而"方框"此时根据数组中是否存在该index调整样式。

    
  • ...
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事件。


...
        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_maskon

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;
}
    
  • ...

这里需要注意,当代理拖动到最左边或最右边时,由于布局是inline-block,此时目标位置所在行的上一行(如果有)的最后一个单元格或下一行(如果有)的第一个单元格也会发生偏移。

解决方法是设置变量x_index,表示单元格在x方向的index.在添加偏移class的时候,增加判定条件。

  • ...
  • mouseup
            function onMouseUp(target){
                if(photo_sort.drag_flag){
                    for(var i=0,len=photo_sort.selected_index.size();itarget_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.

    可以看到,拖动第2,3张图片到第8,9张图片之间,结果应该是第8张图片在第二行最左边,然后向右依次是第2,3张图片,最后是第9张图片.而这里显然不是想要的结果。

    根源

    出现这个问题的原因在于
    依次移动目标图片到目标位置,对多张目标图片,会有一个移动先后的考量。具体的,

    假设上图要移动的目标图片是第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
    

    还是在依次遍历目标图片,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;jtarget_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在细节上还做了

    框选图片

    如果有滚动条,且拖动位置快要超出当前界面,滚动条会自动上移或下移。
    这两个本屌就不做了,原理也是很简单的。

    下载

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/86002.html

    相关文章

    • avalon js实现仿google plus图片多张拖动排序

      摘要:支持多张图片拖动排序。解决方法是设置变量表示单元格在方向的在添加偏移的时候,增加判定条件。根源出现这个问题的原因在于依次移动目标图片到目标位置,对多张目标图片,会有一个移动先后的考量。 效果 showImg(https://segmentfault.com/img/bVp0ye);showImg(https://segmentfault.com/img/bVp0GA);拖动+响应式效果...

      pekonchan 评论0 收藏0
    • avalon js+css3实现roundabout 图片轮播

      摘要:效果效果就像优酷综艺频道页面的图片轮播。本屌之前做过这个参见仿优酷频道首页的图片切换效果不过用的是类似的库做的。 roundabout效果 效果就像优酷综艺频道页面的图片轮播。本屌之前做过这个roundabout,参见仿优酷频道首页的图片切换效果,不过用的是类似jquery的库做的。尽管js代码不到200行,但还是显得有点复杂。于是乎,本屌盘算着可不可以用更少的代码完成这个效果。顺便说...

      AlphaGooo 评论0 收藏0
    • avalon js+css3实现roundabout 图片轮播

      摘要:效果效果就像优酷综艺频道页面的图片轮播。本屌之前做过这个参见仿优酷频道首页的图片切换效果不过用的是类似的库做的。 roundabout效果 效果就像优酷综艺频道页面的图片轮播。本屌之前做过这个roundabout,参见仿优酷频道首页的图片切换效果,不过用的是类似jquery的库做的。尽管js代码不到200行,但还是显得有点复杂。于是乎,本屌盘算着可不可以用更少的代码完成这个效果。顺便说...

      Awbeci 评论0 收藏0
    • MV* 框架 与 DOM操作为主 JS库 的案例对比

      摘要:中定义的处理业务逻辑与提供数据源,中的绑定负责渲染与响应用户点击拖拽等行为,这样就最大保证了视图逻辑相分离。远离的世界,围绕层控制器路由从后端放到前端,更加适合开发。 最近分别使用 Zepto 和 Avalon框架写了个 SPA项目,贴出来讨论下 JS DOM操作为主 JS库 与 MV* 框架的对比 案例(MV* 框架 与 DOM操作 JS库 实例对比) 购物车页面 JS业务逻辑...

      springDevBird 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <