avalon js实现仿google plus图片多张拖动排序附源码下载
作者:TheViper_ 发布时间:2024-04-30 09:52:27
源码下载:http://xiazai.aspxhome.com/201509/yuanma/drag_sort1(aspxhome.com).rar
效果展示如下:
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在细节上还做了
- 框选图片
- 如果有滚动条,且拖动位置快要超出当前界面,滚动条会自动上移或下移。
这两个本 * 就不做了,原理也是很简单的。
猜你喜欢
- 1、引言小丝:鱼哥, 请教你个问题。小鱼:你觉得你得问题,是正儿八经的吗?小丝:那必须的, 人都正经,何况问题呢?小鱼:那可不敢说, 你得问
- 本文实例讲述了javascript实现Table排序的方法。分享给大家供大家参考。具体实现方法如下:<!DOCTYPE html PU
- 本来想把之前对artTemplate源码解析的注释放上来分享下,不过隔了一年,找不到了,只好把当时分析模板引擎原理后,自己尝试写下的模板引擎
- >>> "hello".encode("hex") '68656c6c6f
- (一)前言众所周知,Navicat是我们常用的连接MYSQL工具,非常方便好用。其实日常中,我们也常常会遇到运行时间很长甚至几乎跑不完卡死的
- 看到很多站长工具网,都提供了通过域名获取网站IP的方法。自己也想做一个,网上查了不少代码。有说用WSHSHELL,也有说用ASPPING组件
- 1.match() 从开始位置开始匹配 2.search() 任意位置匹配,如果有多个匹配,只返回第一个 3.finditer() 返回所有
- 二叉树的反序列化反序列化树的反序列化故名知意就是将一个序列化成字符串或者其它形式的数据重新的生成一颗二叉树,如下这颗二叉树将它序列化成字符串
- 我们知道IE6是不支持透明的PNG的,这无疑限制了网页设计的发挥空间.然而整个互联网上解决这个IE6的透明PNG的方案也是多不胜数,从使用I
- 本篇我们将学习简单的json数据的存储首先我们需要引入json模块:import json这里我们模拟一个常见常见,我们让用户输入用户名、密
- Pivot 及 Pivot_table函数用法Pivot和Pivot_table函数都是对数据做透视表而使用的。其中的区别在于Pivot_t
- 前言本文仅仅介绍了常见的一些JS加密,并记录了JS和Python的实现方式常见的加密算法基本分为这几类:(1)base64编码伪加密(2)线
- 类似于and操作类似于or操作# 类型转换# sortedli=[2,45,1,67,23,10]li.sort() #list的排序方法p
- 1、修改MD5算法重的4个常数,这是最捷径的作法,其特点是加密后的数据和加密前非常类似,但是不会被破解 2、多次加密,对MD5加密过的数据进
- 详解微信小程序中的页面代码中的模板的封装 最近在进行微信小程序中的页面开发,其实在c++或者说是js中都
- 前言最近在看测试相关的内容,发现自动化测试很好玩,就决定做一个自动回复QQ消息的脚本(我很菜)1、需要安装的模块这个自动化脚本需要用到3个模
- 一、基本思想本文思想是基于用asp和DOM来读取和存储XML数据,并利用XML数据来存储留言信息,达到同用数据库存储数据的功能。二、XML留
- 相关文章推荐:各种北京2008奥运会倒计时Flash2008北京奥运会倒计时js代码 全套北京2008奥运会倒计时屏保<!DOCTYP
- 死锁是指在某组资源中,两个或两个以上的线程在执行过程中,在争夺某一资源时而造成互相等待的现象,若无外力的作用下,它们都将无法推进下去,死时就
- 1、词表映射无论是深度学习还是传统的统计机器学习方法处理自然语言,都需要先将输入的语言符号(通常为标记Token),映射为大于等于0、小于词