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 相关文章推荐
基于Jquery的简单图片切换效果
Jan 06 Javascript
js 操作select与option(示例讲解)
Dec 20 Javascript
jQuery aminate方法定位到页面具体位置
Dec 26 Javascript
AngularJS基础 ng-srcset 指令简单示例
Aug 03 Javascript
javascript使用闭包模拟对象的私有属性和方法
Oct 05 Javascript
bootstrap weebox 支持ajax的模态弹出框
Feb 23 Javascript
JS对象的深度克隆方法示例
Mar 16 Javascript
js控制文本框禁止输入特殊字符详解
Apr 07 Javascript
JavaScript仿微信打飞机游戏
Jul 05 Javascript
JavaScript无操作后屏保功能的实现方法
Jul 04 Javascript
详解AngularJS跨页面传值(ui-router)
Aug 23 Javascript
jfinal与bootstrap的登出实战详解
Nov 27 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
PHP中集成PayPal标准支付的实现方法分享
2012/02/06 PHP
php验证手机号码(支持归属地查询及编码为UTF8)
2013/02/01 PHP
PHP中执行cmd命令的方法
2014/10/11 PHP
PHP创建/删除/复制文件夹、文件
2016/05/03 PHP
php之header的不同用法总结(实例讲解)
2017/11/28 PHP
tp5修改(实现即点即改)
2019/10/18 PHP
PHP之header函数详解
2021/03/02 PHP
jQuery 类twitter的文本字数限制带提示效果插件
2010/04/16 Javascript
微博@符号的用户名提示效果。(想@到谁?)
2010/11/05 Javascript
jQuery弹出层始终垂直居中相对于屏幕或当前窗口
2013/04/01 Javascript
Javascript中使用A标签获取当前目录的绝对路径方法
2015/03/02 Javascript
JQuery菜单效果的两个实例讲解(3)
2015/09/17 Javascript
JS+HTML5手机开发之滚动和惯性缓动实现方法分析
2016/06/12 Javascript
原生JS发送异步数据请求
2017/06/08 Javascript
让你彻底掌握es6 Promise的八段代码
2017/07/26 Javascript
JavaScript实现仿Clock ISO时钟
2018/06/29 Javascript
微信小程序全局变量改变监听的实现方法
2019/07/15 Javascript
[15:56]Heroes18_暗影萨满(完美)
2014/10/31 DOTA
Python中使用socket发送HTTP请求数据接收不完整问题解决方法
2015/02/04 Python
Python 实现简单的shell sed替换功能(实例讲解)
2017/09/29 Python
Python学习笔记之抓取某只基金历史净值数据实战案例
2019/06/03 Python
Python文件路径名的操作方法
2019/10/30 Python
python计算Content-MD5并获取文件的Content-MD5值方式
2020/04/03 Python
Jupyter Notebook折叠输出的内容实例
2020/04/22 Python
跑鞋、网球鞋、网球拍、服装及装备:Holabird Sports
2016/09/19 全球购物
小橄榄树:Le Petit Olivier
2018/04/23 全球购物
美国香薰蜡烛品牌:PADDYWAX
2018/10/06 全球购物
TUMI新加坡官网:国际领先的商旅箱包品牌
2019/01/12 全球购物
澳大利亚珠宝商:Shiels
2019/10/06 全球购物
商务日语毕业生自荐信范文
2013/11/14 职场文书
优秀辅导员事迹材料
2014/02/16 职场文书
秋季运动会广播稿大全
2014/02/17 职场文书
读书活动总结
2014/04/28 职场文书
2016年春季运动会广播稿
2015/08/19 职场文书
党员廉政准则心得体会
2016/01/20 职场文书
MySQL 数据库 增删查改、克隆、外键 等操作
2022/05/11 MySQL