avalon js实现仿google plus图片多张拖动排序附源码下载


Posted in Javascript onSeptember 24, 2015

源码下载:http://xiazai.3water.com/201509/yuanma/drag_sort1(3water.com).rar

效果展示如下:

google plus

 avalon js实现仿google plus图片多张拖动排序附源码下载

 avalon js实现仿google plus图片多张拖动排序附源码下载

拖动+响应式效果:

 avalon js实现仿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`

具体参见本?诺哪D?lexbox 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`.后面可以看到单元格里面都是百分比,需要在外面限定最大尺寸。

图片响应式+水平垂直居中

具体参见本?诺?ss图片响应式+垂直水平居中

选中图片

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();
}

几点说明
- 关于当前拖动到的位置判定

 avalon js实现仿google plus图片多张拖动排序附源码下载

图中每个单元格的竖线,在水平方向把单元格分为两边。每个竖线把一行分为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`,此时目标位置所在行的上一行(如果有)的最后一个单元格或下一行(如果有)的第一个单元格也会发生偏移。

 avalon js实现仿google plus图片多张拖动排序附源码下载

解决方法是设置变量`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();
   }
  }

这里主要就是对图片列表的重排。
- 目标位置在选中图片之前

 avalon js实现仿google plus图片多张拖动排序附源码下载

先把原始图片保存在`temp`,然后把从目标位置图片到原始图片前一位置的图片,依次后移一个位置,最后把`temp`放到目标位置。
- 目标位置在选中图片之后

 avalon js实现仿google plus图片多张拖动排序附源码下载

和上面差不多,只不过这里是把从目标位置图片到原始图片后一位置的图片,依次前移一个位置。

注意

不能像`data[j]=data[j+1]`这样赋值,因为avalon不支持单个转换,如果想更新,需要将整个子VM重新赋以一个新的对象。也就是定义一个arr,然后从头开始向里面添加model,最后`photo_sort.photo_list.clear()`删除所有图片,`photo_sort.photo_list=arr`重新赋值,更新视图。

后记

事实上,google plus在细节上还做了
- 框选图片
- 如果有滚动条,且拖动位置快要超出当前界面,滚动条会自动上移或下移。
这两个本?啪筒蛔隽耍??硪彩呛芗虻サ摹?/p>

Javascript 相关文章推荐
JSON 学习之完全手册 图文
May 29 Javascript
javascript入门基础之私有变量
Feb 23 Javascript
Google的跟踪代码 动态加载js代码方法应用
Nov 12 Javascript
js函数获取html中className所在的内容并去除标签
Sep 08 Javascript
JavaScript常用脚本汇总(三)
Mar 04 Javascript
JS实现选中当前菜单后高亮显示的导航条效果
Oct 15 Javascript
jQuery实现产品对比功能附源码下载
Aug 09 Javascript
javascript高级模块化require.js的具体使用方法
Oct 31 Javascript
Node.js笔记之process模块解读
May 31 Javascript
vue 使用vue-i18n做全局中英文切换的方法
Oct 29 Javascript
Node.js学习教程之Module模块
Sep 03 Javascript
vue实现日历表格(element-ui)
Sep 24 Javascript
JS+CSS实现简易的滑动门效果代码
Sep 24 #Javascript
JS实现网站菜单拖拽移位效果的方法
Sep 24 #Javascript
jQuery实现的经典竖向伸缩菜单效果代码
Sep 24 #Javascript
JS+CSS实现经典的左侧竖向滑动菜单效果
Sep 23 #Javascript
直接拿来用的15个jQuery代码片段
Sep 23 #Javascript
JS实现漂亮的淡蓝色滑动门效果代码
Sep 23 #Javascript
jQuery Validate验证框架经典大全
Sep 23 #Javascript
You might like
收音机频率指针指示不准确和灵敏度低问题
2021/03/02 无线电
php目录遍历函数opendir用法实例
2014/11/20 PHP
php+mysqli使用面向对象方式更新数据库实例
2015/01/29 PHP
Yii实现自动加载类地图的方法
2015/04/01 PHP
ExtJS 入门
2010/10/29 Javascript
js对象的构造和继承实现代码
2010/12/05 Javascript
javascript操作JSON的要领总结
2012/12/09 Javascript
JQuery each()函数如何优化循环DOM结构的性能
2012/12/10 Javascript
JS中产生20位随机数以0-9为例也可以是a-z A-Z
2014/08/01 Javascript
js canvas实现擦除动画
2016/07/16 Javascript
js实现右键自定义菜单
2016/12/03 Javascript
详解Javascript数据类型的转换规则
2016/12/12 Javascript
Vue.js实现网格列表布局转换方法
2017/08/25 Javascript
微信小程序wepy框架笔记小结
2018/08/08 Javascript
详解如何使用router-link对象方式传递参数?
2019/05/02 Javascript
VUE+elementui面包屑实现动态路由详解
2019/11/04 Javascript
vue 使用原生组件上传图片的实例
2020/09/08 Javascript
[42:22]DOTA2上海特级锦标赛C组小组赛#1 OG VS Archon第一局
2016/02/27 DOTA
Python实现的金山快盘的签到程序
2013/01/17 Python
解决谷歌搜索技术文章时打不开网页问题的python脚本
2013/02/10 Python
Python中使用copy模块实现列表(list)拷贝
2015/04/14 Python
Python使用chardet判断字符编码
2015/05/09 Python
python+matplotlib实现动态绘制图片实例代码(交互式绘图)
2018/01/20 Python
在Python 字典中一键对应多个值的实例
2019/02/03 Python
Django后台admin的使用详解
2019/07/08 Python
Django的性能优化实现解析
2019/07/30 Python
Python操作Sqlite正确实现方法解析
2020/02/05 Python
selenium+python实现基本自动化测试的示例代码
2021/01/27 Python
Topshop美国官网:英国快速时尚品牌
2019/05/16 全球购物
.NET方向面试题
2014/11/20 面试题
Java语言程序设计测试题改错题部分
2014/07/22 面试题
Java如何支持I18N?
2016/10/31 面试题
2014年九一八事变演讲稿
2014/09/14 职场文书
党员自我评议个人对照检查材料
2014/09/16 职场文书
2015年学生管理工作总结
2015/05/26 职场文书
TensorFlow的自动求导原理分析
2021/05/26 Python