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

转载请注明: TheViper http://www.cnblogs.com/TheViper 

效果

google plus

拖动+响应式效果:

要求

1. 两边对齐布局,即图片间间距一致,但左右两边的图片与边界的间距不一定等于图片间间距,兼容ie7,8,firefox,chrome.
2. 浏览器尺寸变化,在大于一定尺寸时,每行自动增加或减少图片,自动调整图片间间距,以满足两边对齐布局,这时每张图片尺寸固定(这里是200*200px);而小于一定尺寸时,每行图片数量固定(这里最小列数是3),这时图片总是等比例拉伸或缩放。
3. 浏览器不同尺寸下,仍然可以拖动排序。
4. 图片,拖动代理里的图片始终保持等比例且水平垂直居中。
5. 拖动到相应位置时,位置左右的图片发生一定偏移。如果在最左边或最右边,则只是该行的第一张图片或最后一张图片发生偏移。
6. 支持多张图片拖动排序。

实现

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

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图片响应式+垂直水平居中

选中图片

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_index.indexOf($index)>-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;
        if(selected_index.indexOf(i)==-1)//选中图片的index列表不存在,添加
            photo_sort.selected_index.ensure(i);
        else
            photo_sort.selected_index.remove(i);
    }
});

mousedown

这里用了遮罩层,并在上面绑定mousedown事件。

<a href="javascript:;" class="no_selected" ms-class-selected_icon=‘selected_index.indexOf($index)>-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_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.target_index=-1;//各种重置,初始化
                photo_sort.sort_array=[];
                photo_sort.col_num=0;
                photo_sort.x_index=-1;
                photo_sort.selected_index=[];
                $(‘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重新赋以一个新的对象。也就是定义一个arr,然后从头开始向里面添加model,最后`photo_sort.photo_list.clear()`删除所有图片,`photo_sort.photo_list=arr`重新赋值,更新视图。

后记

事实上,google plus在细节上还做了
- 框选图片
- 如果有滚动条,且拖动位置快要超出当前界面,滚动条会自动上移或下移。
这两个本屌就不做了,原理也是很简单的。

时间: 2024-12-25 02:04:14

avalon js实现仿google plus图片多张拖动排序的相关文章

avalon js拖动图片排序

转载请注明: TheViper http://www.cnblogs.com/TheViper   什么是拖动图片排序? 就像微博这种,上传后允许用户通过拖动图片,调整几张图片的顺序. 可以看到微博在这里把每张图片固定了尺寸,稍微严谨点的话,就需要像上一篇文章那样,外面是响应式的等高等宽的若干div容器,里面则是等比例缩放的响应式图片. 下面说下要求. 1.当然首先图片要可以拖动. 2.图片移出其原本的位置,拖动到目标位置并且未松开鼠标完成拖动前,需要在目标位置设置占位符,让用户预览拖动完成后的

网页模块拖动,Js仿Google和Windows Live的层拖拽

<HTML><HEAD><TITLE>仿Google和Windows Live的拖拽</TITLE><style type="text/css">body{ margin:10px;}#dragHelper{ position:absolute;/*重要*/ border:2px dashed #000000; background-color:#FFFFFF;filter: alpha(opacity=30);}.norma

MVC、MVP、MVVM、Angular.js、Knockout.js、Backbone.js、React.js、Ember.js、Avalon.js 概念摘录

转自:http://www.cnblogs.com/xishuai/p/mvc-mvp-mvvm-angularjs-knockoutjs-backbonejs-reactjs-emberjs-avalonjs.html MVC MVC(Model-View-Controller),M 是指业务模型,V 是指用户界面,C 则是控制器,使用 MVC 的目的是将 M 和 V 的实现代码分离,从而使同一个程序可以使用不同的表现形式. 交互方式(所有通信都是单向的): View 传送指令到 Contro

avalon.js实践 svg地图配置工具

MVVM模式,在很多复杂交互逻辑下面,有很大的优势.现在相关的框架也很多,现在项目中使用了avalon.js,选择它的原因,是兼容性的考虑,当然也要支持下国内开发大牛,至于性能方面的,没有实际测试过,不敢在这边说明. 需求:项目中首页需要显示地图,显示公司在该区域部署的教室数量,首页可以显示行政中心,区域名字,加入★着重的地市 前台:页面显示采用了raphael.js,把地图信息js,转换成vml,svg(百度的eChart的地图也不错,但是它没有区县下面的数据,没有采用) 问题:美术妹妹出的都

PHP仿微信多图片预览上传功能

PHP仿微信多图片预览下载演示地址:http://www.erdangjiade.com/js...生产图片区域,上传按钮#btn可替换自己想要的图片 [html] view plain copy在CODE上查看代码片派生到我的代码片 <ul id="ul_pics" class="ul_pics clearfix"> <li><img src="logo.png" id="btn" class=

一款轻量级前端框架Avalon.Js

avalon2是一款基于虚拟DOM与属性劫持的 迷你. 易用. 高性能 的 前端MVVM框架, 拥有超优秀的兼容性, 支持移动开发, 后端渲染, WEB Component式组件开发, 无需编译, 开箱即用.上一个简单的入门例子: <!DOCTYPE html> <html> <head> <title>first example</title> <meta charset="UTF-8"> <meta na

JS跟随鼠标旋转的图片

<html> <head> <title>JS跟随鼠标旋转的图片丨Daniel wellington</title> <script> <!-- Beginning of JavaScript - var x,y var step=5 var flag=1 var pause var timersmall var timerbig var isbigcircle=1 var pause=50 var bigradius var smallr

CSS3实战开发:仿天猫首页图片展示动画特效实战开发

各位网友大家好,我是陌上花会开,人称陌陌.今天我带领大家开发一个仿天猫首页图片展示动画特效的案例.一如往常,我既不提供源码给大家,也不提供Demo给大家,只是希望大家能跟着我的讲解,然后将代码一步步复制到本地,本人可以保证,页面上的代码复制到本地,绝对百分百运行,且得到与陌陌一样的运行效果.我这么做只为激起大伙的动手能力,望大家能明白我的用心. 好了,不废话了,直接本篇的实战开发吧. 我们看一下我们今天要做的实战案例效果图: 1. 鼠标划过前: 2. 鼠标划过右侧的图片时: 可能大伙看这个静态截

PHP实现仿Google分页效果的分页函数

本文实例讲述了PHP实现仿Google分页效果的分页函数.分享给大家供大家参考.具体如下: /** * 分页函数 * @param int $total 总页数 * @param int $pagesize 每页几条 * @param string $pkey url中页面变量名称 * @param string $url 基础url,其中的{page}会被替换为页码 * 依赖函数 request_uri httpInt */ function page($total,$pagesize=10,