原生js的拖拽排序,如何保存拖拽后的位置?

  • 10
<ul id="main" class="main" (dragover)="allowDrop($event)">
   <li draggable="true" (dragstart)="dragStart($event)" (dragend)="dragEnd($event)"  class="card" *ngFor="let item of list">
           {{item.name}}
   </li>
</ul>



ngAfterViewInit() {
}

// 拖拽开始
dragStart(event) {
    this.draging = event.target
    event.dataTransfer.getData(this.draging)
}

// 拖拽中
allowDrop(event) {
    // 默认无法将数据/元素放置到其他元素中。如果需要设置允许放置,必须阻止对元素的默认处理方式
    event.preventDefault();
    var target = event.target;

    if (target.nodeName === "LI" && target !== this.draging) {
        var targetRect = target.getBoundingClientRect();
        var dragingRect = this.draging.getBoundingClientRect();

        if (target) {
            // 判断是否动画元素
            if (target.animated) {
                return;
            }
        }
        if (this.index(this.draging) < this.index(target)) {
            // 目标比元素大,插到其后面
            // nextSibling下一个兄弟元素
            target.parentNode.insertBefore(this.draging, target.nextSibling)
        } else {
            // 目标比元素小,插到其前面
            target.parentNode.insertBefore(this.draging, target)
        }
        this.animate(dragingRect, this.draging)
        this.animate(targetRect, target)
    }
}

// 拖拽完成
dragEnd(event) {
    // var target = event.target;
    // var draging =this.draging;
    // // var tt = JSON.parse(localStorage.getItem('targetRect'));
    // localStorage.setItem('targetRect', JSON.stringify(targetRect));
    // localStorage.setItem('dragingRect', JSON.stringify(dragingRect));
}
// 获取元素在父元素中的index
index(el) {
    var index = 0
    if (!el || !el.parentNode) {
        return -1
    }
    // previousElementSibling:上一个兄弟元素
    while (el && (el = el.previousElementSibling)) {
        index++
    }
    return index
}
// 触发动画
animate(prevRect, target) {
    var ms = 300
    if (ms) {
        var currentRect = target.getBoundingClientRect()
        if (prevRect.nodeType === 1) {
            prevRect = prevRect.getBoundingClientRect()
        }
        this.css(target, 'transition', 'none')
        this.css(target, 'transform', 'translate3d(' +
            (prevRect.left - currentRect.left) + 'px,' +
            (prevRect.top - currentRect.top) + 'px,0)'
        );

        target.offsetWidth; // 触发重绘

        this.css(target, 'transition', 'all ' + ms + 'ms');
        this.css(target, 'transform', 'translate3d(0,0,0)');
        // 事件到了之后把transition和transform清空
        clearTimeout(target.animated);
        target.animated = setTimeout(function () {
            // this.css(target, 'transition', '');
            // this.css(target, 'transform', '');
            target.animated = false;
        }, ms);
    }
}
// 给元素添加style
css(el, prop, val) {
    var style = el && el.style
    if (style) {
        if (!(prop in style)) {
            prop = prop;
        }
        style[prop] = val + (typeof val === 'string' ? '' : 'px')

    }
}

在完成拖拽后能保存完成后的位置,下次加载直接加载这个位置。应该如何保存?

回复
阅读 405
2 个回答

使用数据驱动的框架、按照数据驱动的原则写应用,当顺序变化的时候就不应该通过 insertBefore 这种方案来修改显示顺序,应该拿到被拖动的 DOM 节点对应数据中 list 的哪一项,然后修改这一项在 list 中的顺序:

function move(from, to){
    if(from === to) return;
    const {max, min, abs} = Math;
    // 移动至相邻位的,直接交换
    if(abs(from - to) === 1){
        const temp = list[from];
        list[from] = list[to];
        list[to] = temp;
        // 注意这里要触发一下渲染, 例如在 react 中要调用 setState
        return
    }
      const ahead = +(from > to);
      const tail = list.splice(max(from, to) + 1);
      const body = list.splice(min(from, to));
    if(ahead){
        body.unshift(body.pop());
    } else {
        body.push(body.shift());
    }

    list.push(...body, ...tail);
    // 注意这里要触发一下渲染, 例如在 react 中要调用 setState
}

这样的话,排序完成的同时就已经知晓 list 的变化了,所以顺便把这个 list 转为字符串保存到 localStorage,下次加载的时候从 localStorage 里取出来恢复,这样就等于是保存了用户拖拽的结果了。

如果仍采用 insertBefore 的方案,那么你需要将 item 的原始数据与渲染的 DOM绑定,当用户重新排序后,从所有的 li.card 列表中反序列化出排序后的列表,同样采用 localStorage 的方案来本地化:

// 事实上我不会 ng,所以下面的写法是我猜测的,希望你能看懂,
// 然后以正确的方式写出来index,是索引
<li draggable="true"
    (dragstart)="dragStart($event)"
    (dragend)="dragEnd($event)"
    class="card"
    *ngFor="let item of list"
    origin-index="{{index}}"
>
           {{item.name}}
   </li>

// 在你每次拖拽排序完成后
const newList = [].map.apply(
    target.parentNode,
    [i => list[i.getAttribute('origin-index')]]
)
// 接下来存储这个 newList

还有一种更绝的方案:

把生成的 DOM 直接存起来!

后台返回的数据的话,应该由后台存储位置信息,如果前端改变了位置,应该发送请求去改变位置信息。

你知道吗?

宣传栏