# Sortable改良版
Sortable是一个用于可重新排序的拖放列表的JavaScript库.

演示: http://sortablejs.github.io/Sortable/


## 特征

 * 支持触摸设备和 [现代](http://caniuse.com/#search=drag) 浏览器（包括IE9）
 * 可以从一个列表拖动到另一个列表或在同一列表中
 * 移动项目时的CSS动画
 * 支持拖动手柄和可选文本
 * 智能自动滚动
 * 高级交换检测
 * 使用原生HTML5拖放API构建
 * 支持任何CSS库，例如[Bootstrap](#bs)
 * 简单的API
 * 不需要jQuery


<br/>

### 安装

通过 npm

```bash
$ npm install sortable2 --save
```

通过 bower:

```bash
$ bower install --save sortable2
```

<br/>

### 用法
```html
<ul id="items">
	<li>item 1</li>
	<li>item 2</li>
	<li>item 3</li>
</ul>
```

```js
var el = document.getElementById('items');
var sortable = Sortable.create(el);
```

您可以使用列表及其元素的任何元素，而不仅仅是 `ul`/`li`.

---


### 选项
```js
var sortable = new Sortable(el, {
	//  或{name：“...”，pull：[true，false，'clone'，array]，put：[true，false，array]}
	group: "name",  
	//  在列表内排序
	sort: true,  
	// 以毫秒为单位定义何时开始排序  
	delay: 0, 
	// 仅在用户使用触摸时才会延迟 
	delayOnTouchOnly: false, 
	//  px，在取消延迟拖动事件之前该点应移动多少像素
	touchStartThreshold: 0, 
	// 如果设置为true ，则禁用sortable.
	disabled: false, 
	// @see 存储
	store: null,  
	// ms，排序时动画速度移动项目，“0” - 没有动画
	animation: 150,  
	// 缓动动画。默认为null。有关 示例，请参阅  https://easings.net/。
	easing: "cubic-bezier(1, 0, 0, 1)", 
	// 在列表项中拖动句柄选择器 
	handle: ".my-handle",  
	// 不会导致拖动的选择器（String或Function）   
	filter: ".ignore-elements",  
  //  触发`filter`时调用`event.preventDefault（） 
	preventOnFilter: true, 
	// 指定元素内的哪些项应该是可拖动的  
	draggable: ".item",  
	// drop placeholder的类名
	ghostClass: "sortable-ghost",  
	// 所选项目的类名称  
	chosenClass: "sortable-chosen",  
	// 拖动项的类名   
	dragClass: "sortable-drag",  
	dataIdAttr: 'data-id',

  // 交换区域的阈值
	swapThreshold: 1, 
	// 如果设置为true ，将始终使用反向交换区域 
	invertSwap: false, 
	// 反向交换区域的阈值（默认设置为swapThreshold值）
	invertedSwapThreshold: 1, 
	// 锁定拖拉方向
	direction: 'xy', 

  // 忽略HTML5 拖拉行为并强制回退   
	forceFallback: false,  

  // 使用forceFallback时克隆的DOM元素的类名   
	fallbackClass: "sortable-fallback",  
	// 将克隆的DOM元素追加到Document的Body中 
	fallbackOnBody: false,  
	// 像素为单位指定鼠标在被视为拖动之前应移动多远。  
	fallbackTolerance: 0, 

  // or HTMLElement
	scroll: true, 
	// 如果你有自定义滚动条scrollFn可用于自动滚动
	scrollFn: function(offsetX, offsetY, originalEvent, touchEvt, hoverTargetEl) { ... }, 
	// px, 鼠标必须靠近边缘才能开始滚动。  
	scrollSensitivity: 30, 
	// px
	scrollSpeed: 10, 
	//  将 autoscroll 应用于所有父元素，以便更轻松地移动
	bubbleScroll: true, 

	dragoverBubble: false,
	 // 删除未显示的克隆元素，而不是仅隐藏它
	removeCloneOnHide: true,
	// px，distance mouse必须是空的sortable才能将drag元素插入其中  
	emptyInsertThreshold: 5, 


	setData: function (/** DataTransfer */dataTransfer, /** HTMLElement*/dragEl) {
		dataTransfer.setData('Text', dragEl.textContent); // HTML5 DragEvent的`dataTransfer`对象 
	},

	// 元素被选中
	onChoose: function (/**Event*/evt) {
		evt.oldIndex;  // 父元素索引
	},

	// 元素是非选择的
	onUnchoose: function(/**Event*/evt) {
		// 与onEnd相同的属性
	},

	// 元素拖动已开始
	onStart: function (/**Event*/evt) {
		evt.oldIndex;  // 父元素索引
	},

	// 元素拖动结束
	onEnd: function (/**Event*/evt) {
		var itemEl = evt.item;  // 拖动HTMLElement 
		evt.to;    // 目标列表    
		evt.from;  // 上一个清单  
		evt.oldIndex;  // 元素在旧父级中的旧索引  
		evt.newIndex;  // 元素在新父级中的新索引
		evt.oldDraggableIndex; // 元素在旧父元素中的旧索引，仅计算可拖动元素 
		evt.newDraggableIndex; // 元素在新父元素中的新索引，仅计算可拖动元素 
		evt.clone // 克隆元素 
		evt.pullMode;  // 当item在另一个sortable中时：`clone`如果克隆，`true`如果移动  
	},

	// 元素从另一个列表中放入列表中
	onAdd: function (/**Event*/evt) {
		// 与onEnd相同的属性
	},

	// 更改了列表中的排序
	onUpdate: function (/**Event*/evt) {
		// 与onEnd相同的属性
	},

	// 通过列表的任何更改调用（添加/更新/删除）
	onSort: function (/**Event*/evt) {
		// 与onEnd相同的属性
	},

	// 元素从列表中删除到另一个列表中
	onRemove: function (/**Event*/evt) {
		// 与onEnd相同的属性
	},

	// 尝试拖动已过滤的元素
	onFilter: function (/**Event*/evt) {
		var itemEl = evt.item;  //  HTMLElement接收 `mousedown|tapstart` 事件.
	},

	// 在列表中或列表之间移动项目时的事件
	onMove: function (/**Event*/evt, /**Event*/originalEvent) {
		evt.dragged; // 拖动HTMLElement 
		evt.draggedRect; // DOMRect {left, top, right, bottom}
		evt.related; // 引导的HTMLElement 
		evt.relatedRect; // DOMRect
		evt.willInsertAfter; // 如果Sortable默认情况下会在目标之后插入drag元素，则为true
		originalEvent.clientY; // 鼠标位置
		//  返回false;  - 取消
		//  返回-1;  - 在目标之前插入
		//  返回1;  - 插入目标后
	},

	// 在创建元素克隆时调用
	onClone: function (/**Event*/evt) {
		var origEl = evt.item;
		var cloneEl = evt.clone;
	},

	// 拖动元素更改位置时调用
	onChange: function(/**Event*/evt) {
		evt.newIndex // 最有可能使用此事件的原因是获取拖动元素的当前索引 
		// 与onEnd相同的属性
	}
});
```


---


#### `group` 选项
要将元素从一个列表拖动到另一个列表，两个列表必须具有相同的`group`值。您还可以定义列表是否可以放弃，提供和保留副本（`clone`）以及接收元素。

 * name: `String` — 组名
 * pull: `true|false|["foo", "bar"]|'clone'|function` — 从列表中移动的能力. `clone` — 复制项目，而不是移动。或者可以放入元素的组名称数组。默认为 `true`.
 * put: `true|false|["baz", "qux"]|function` — 是否可以从其他列表添加元素，或者是否可以添加元素的组名称数组.
 * revertClone: `boolean` — 移动到另一个列表后，将克隆元素恢复到初始位置


演示:
 - https://jsbin.com/hijetos/edit?js,output
 - https://jsbin.com/nacoyah/edit?js,output — 在`pull`和中使用复杂逻辑`put`
 - https://jsbin.com/bifuyab/edit?js,output — 使用 `revertClone: true`


---


#### `sort` 选项
在列表内排序。

演示: https://jsbin.com/jayedig/edit?js,output


---


#### `delay` 选项
定义何时开始排序的时间（以毫秒为单位）。不幸的是，由于浏览器的限制，在本机拖放的IE或Edge上无法延迟。

演示: https://jsbin.com/zosiwah/edit?js,output


---


#### `delayOnTouchOnly` 选项
是否应仅在用户使用触摸时（例如，在移动设备上）应用延迟。在任何其他情况下都不会延迟。默认为 `false`.


---


#### `swapThreshold` 选项
交换区域将占用的目标的百分比，作为0和之间的浮点数1

阅读更多: https://github.com/SortableJS/Sortable/wiki/Swap-Thresholds-and-Direction#swap-threshold

演示: http://sortablejs.github.io/Sortable#thresholds


---


#### `invertSwap` 选项
设置为`true`将交换区域设置为目标的两侧，以便对“介于”项目之间进行排序

演示: http://sortablejs.github.io/Sortable#thresholds


---


#### `invertedSwapThreshold` 选项
反转交换区域将占用的目标百分比，作为`0`和之间的浮点数`1`。如果没有给出，将默认为 `swapThreshold`.

Read more: https://github.com/SortableJS/Sortable/wiki/Swap-Thresholds-and-Direction#dealing-with-swap-glitching


---


#### `direction` 选项
拖拉方向排序

---


#### `touchStartThreshold` 选项
此选项与`fallbackTolerance`选项类似。

`delay`设置此选项后，即使您的手指没有移动，
某些具有非常敏感触摸显示屏的手机（如Samsung Galaxy S8）也会触发不需要的触摸移动事件，从而导致排序无法触发。
此选项设置在取消延迟排序之前必须发生的最小指针移动

3 到 5 是最佳值.

---


#### `disabled` 选项
如果设置为，则禁用sortable `true`.

演示: https://jsbin.com/sewokud/edit?js,output

```js
var sortable = Sortable.create(list);

document.getElementById("switcher").onclick = function () {
	var state = sortable.option("disabled"); // get

	sortable.option("disabled", !state); // set
};
```


---


#### `handle` 选项
要使列表项可拖动，Sortable将禁用用户的文本选择。
这并不总是令人满意的。要允许文本选择，请定义一个拖动处理程序，它是每个列表元素的一个区域，允许它被拖动

演示: https://jsbin.com/numakuh/edit?html,js,output

```js
Sortable.create(el, {
	handle: ".my-handle"
});
```

```html
<ul>
	<li><span class="my-handle">::</span> list item text one
	<li><span class="my-handle">::</span> list item text two
</ul>
```

```css
.my-handle {
	cursor: move;
	cursor: -webkit-grabbing;
}
```


---


#### `filter` 选项


```js
Sortable.create(list, {
	filter: ".js-remove, .js-edit",
	onFilter: function (evt) {
		var item = evt.item,
			ctrl = evt.target;

		if (Sortable.utils.is(ctrl, ".js-remove")) {  // Click on remove button
			item.parentNode.removeChild(item); // remove sortable item
		}
		else if (Sortable.utils.is(ctrl, ".js-edit")) {  // Click on edit link
			// ...
		}
	}
})
```


---


#### `ghostClass` 选项
放置占位符的类名（默认 `sortable-ghost`).

演示: https://jsbin.com/henuyiw/edit?css,js,output

```css
.ghost {
  opacity: 0.4;
}
```

```js
Sortable.create(list, {
  ghostClass: "ghost"
});
```


---


#### `chosenClass` 选项
所选项目的类名称（默认`sortable-chosen`）.

演示: https://jsbin.com/hoqufox/edit?css,js,output

```css
.chosen {
  color: #fff;
  background-color: #c00;
}
```

```js
Sortable.create(list, {
  delay: 500,
  chosenClass: "chosen"
});
```


---


#### `forceFallback` 选项
如果设置为`true`，即使我们使用HTML5浏览器，也将使用非HTML5浏览器的后备。
这使我们有可能测试旧浏览器的行为，即使在较新的浏览器中，也可以使Drag'n Drop在桌面，移动和旧浏览器之间更加一致。
最重要的是，Fallback总是生成该DOM元素的副本，并附加`fallbackClass`选项中定义的类。此行为控制此“拖动”元素的外观.

演示: https://jsbin.com/sibiput/edit?html,css,js,output


---


#### `fallbackTolerance` 选项
模拟本机阻力阈值。
以像素为单位指定鼠标在被视为拖动之前应移动多远。如果项目也可以在链接列表中单击，则非常有用。
当用户在可排序元素中单击时，在您按下的时间和释放时间之间，您的手移动一点并不罕见。
仅当您将指针移动超过某个公差时才会开始拖动，这样您每次单击时都不会意外地开始拖动.

3 到 5 是最佳值.

---


#### `scroll` 选项
如果设置为true，则页面（或可排序区域）在到达边缘时滚动.

演示:
 - `window`: https://jsbin.com/dosilir/edit?js,output
 - `overflow: hidden`: https://jsbin.com/xecihez/edit?html,js,output


---


#### `scrollFn` 选项
定义将用于自动滚动的功能。默认情况下使用el.scrollTop / el.scrollLeft。有自定义滚动条和专用滚动功能时很有用


---


#### `scrollSensitivity` 选项
定义鼠标必须靠近边缘以开始滚动的方式.


---


#### `scrollSpeed` 选项
鼠标指针进入`scrollSensitivity`距离后窗口应滚动的速度


---


#### `bubbleScroll` 选项
如果设置为`true`，则普通`autoscroll`函数也将应用于用户拖动的元素的所有父元素.

演示: https://jsbin.com/kesewor/edit?html,js,output


---


#### `dragoverBubble` 选项

<br>如果设置为`true`，则dragover事件将冒泡到父排序。
适用于后备和本地dragover事件。默认情况下，
它是`false`，但Sortable只会在元素插入父Sortable后停止冒泡，或者可以插入父Sortable，但不是在特定时间（由于动画等）

---


#### `removeCloneOnHide` 选项
如果设置为`false`，则通过将其CSS `display`属性设置为隐藏克隆 `none`。默认情况下，此选项是`true`，意味着Sortable将在DOM被隐藏时从DOM中删除克隆元素。

---


#### `emptyInsertThreshold` 选项
鼠标必须是空的可排序的距离（以像素为单位），同时拖动要插入到可排序的拖动元素。默认为`5`。设置为`0`禁用此功能.

Demo: https://jsbin.com/becavoj/edit?js,output


---
### 事件对象 ([演示](https://jsbin.com/fogujiv/edit?js,output))

 - to:`HTMLElement` — 目标元素
 - from:`HTMLElement` — 拖动元素
 - item:`HTMLElement` — 拖动dom
 - clone:`HTMLElement`
 - oldIndex:`Number|undefined` — 父级中的旧索引
 - newIndex:`Number|undefined` — 父级内的新索引
 - oldDraggableIndex: `Number|undefined` — 父级中的旧索引，仅计算可拖动元素
 - newDraggableIndex: `Number|undefined` — 父级内的新索引，仅计算可拖动元素
 - pullMode:`String|Boolean|undefined` — 拖拉模式,拖动到另一个可排序（`"clone"`, `true`, or `false`），否则未定义


#### `move` 事件对象
 - to:`HTMLElement`
 - from:`HTMLElement`
 - dragged:`HTMLElement`
 - draggedRect:`DOMRect`
 - related:`HTMLElement` — 引导的元素
 - relatedRect:`DOMRect`
 - willInsertAfter:`Boolean` — `true` 如果将元素插入目标之后（或`false`之前


---


### 方法


##### option(name:`String`[, value:`*`]):`*`
获取或设置选项.


##### closest(el:`String`[, selector:`HTMLElement`]):`HTMLElement|null`
对于集合中的每个元素，通过测试元素本身并遍历DOM树中的祖先来获取与选择器匹配的第一个元素.


##### toArray():`String[]`
将可排序项 `data-id`'的 (`dataIdAttr` 选项) 序列化为字符串数组.


##### sort(order:`String[]`)
根据数组对元素进行排序.

```js
var order = sortable.toArray();
sortable.sort(order.reverse()); // apply
```


##### save()
保存当前排序 (see [store](#存储))


##### destroy()
完全删除可排序功能.


---


<a name="store"></a>
### 存储
保存和恢复排序.

```html
<ul>
	<li data-id="1">order</li>
	<li data-id="2">save</li>
	<li data-id="3">restore</li>
</ul>
```

```js
Sortable.create(el, {
	group: "localStorage-example",
	store: {
		/**
		 * Get the order of elements. Called once during initialization.
		 * @param   {Sortable}  sortable
		 * @returns {Array}
		 */
		get: function (sortable) {
			var order = localStorage.getItem(sortable.options.group.name);
			return order ? order.split('|') : [];
		},

		/**
		 * Save the order of elements. Called onEnd (when the item is dropped).
		 * @param {Sortable}  sortable
		 */
		set: function (sortable) {
			var order = sortable.toArray();
			localStorage.setItem(sortable.options.group.name, order.join('|'));
		}
	}
})
```

---


### 静态方法和属性



##### Sortable.create(el:`HTMLElement`[, options:`Object`]):`Sortable`
创建新实例.


---


##### Sortable.active:`Sortable`
链接到活动实例.


---


##### Sortable.utils
* on(el`:HTMLElement`, event`:String`, fn`:Function`) — 附加事件处理函数
* off(el`:HTMLElement`, event`:String`, fn`:Function`) — 删除事件处理程序
* css(el`:HTMLElement`)`:Object` — 获取所有CSS属性的值
* css(el`:HTMLElement`, prop`:String`)`:Mixed` — 获取样式属性的值
* css(el`:HTMLElement`, prop`:String`, value`:String`) — 设置一个CSS属性
* css(el`:HTMLElement`, props`:Object`) — 设置更多CSS属性
* find(ctx`:HTMLElement`, tagName`:String`[, iterator`:Function`])`:Array` — 按标签名称获取元素
* bind(ctx`:Mixed`, fn`:Function`)`:Function` — 获取一个函数并返回一个始终具有特定上下文的新函数
* is(el`:HTMLElement`, selector`:String`)`:Boolean` — 检查选择器当前匹配的元素集
* closest(el`:HTMLElement`, selector`:String`[, ctx`:HTMLElement`])`:HTMLElement|Null` — 对于集合中的每个元素，通过测试元素本身并遍历DOM树中的祖先来获取与选择器匹配的第一个元素
* clone(el`:HTMLElement`)`:HTMLElement` — 创建匹配元素集的深层副本
* toggleClass(el`:HTMLElement`, name`:String`, state`:Boolean`) — 在每个元素中添加或删除一个类
* detectDirection(el`:HTMLElement`)`:String` — 拖动方向:`x`或`y`获取`xy`