转载

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

本文同步更新在 http://www.cnblogs.com/TheViper/

效果

avalon js实现仿google plus图片多张拖动排序 avalon js实现仿google plus图片多张拖动排序 拖动+响应式效果: http://v.youku.com/v_show/id_XMTM0MjQyMTI0OA==.html

要求

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

简单说,就是

  • 添加一个“多余”的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_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在细节上还做了

  • 框选图片

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

下载

正文到此结束
Loading...