DOMの並び替えアニメーションをしてみたい(追記

(追記ここから)

Enter/Leave とトランジション一覧 — Vue.js

Vue.jsで同じようなやつあるしこれ使ったらええやんという話を聞いた。よさそう。

結果同じような見た目だけど、translateで動かして、そのあとDOMの並びを調整しているっぽい。

データの並び = DOMの並びに完全にリンクしているので、Vueの中でも調整しやすいよね的な感じなのかな。

(追記ここまで)


DOM ががーーーーっとならんだものを並び替えるときにアニメーションできないかなあ、というもの。
うにょーんって動いたら面白いのでは?と思って試してみた。

このページで動く様子が確認できるようにした(動いてるときに再度押すとバグる)

https://gomiba.co.in/blog/tags

 

そのまま動かそうとすると、移動先の座標がわからず動かせないので、いくつかのステップを通してみた。

1. 今の表示されている座標を保存
2. 今の表示されている座標の場所に固定した DOM を生成(この時点ではメモリに乗っているだけで表示されない)
3. コンテンツを並び替えて新しい座標を計算
4. 今の表示されている DOM を非表示 2 で作った DOM を表示する(見え方は変わらないが表示されているものを切り変える)
5. 3 の座標に向けて 2 の DOM を CSS アニメーションで動かす
6. 終わったころあいで 2 と 3 の DOM を入れ替えて 2 の DOM を捨てる。

要は動かす用の DOM を生成して、動かすだけ動かして、前後は元の DOM のまま、というもの。
パッと思いついたやり方はこれ。

 

ちゃんとやろうとすると、再描画が無茶苦茶大変になって遅くなるとおもうので、何か対策考えないといけなさそう。
透過色まずやめよう、とか。

 

コードはこんな感じ。
ちょっと座標のところが面倒になって jQuery を使っている。が、そんなに難しいことはやっていないので生 DOM でもわりかし簡単にいけるはず。

連打対策は入れてないので、連打するとブラウザ固まる。

const DomSortAnimation = ($target, AnimationTimeSec, AnimationCompleteDelaySec, SortFunction) => {
    // fixed now position
    const targetPositions = $target.map((_, e) => {
        const $t = $(e);
        return $t.position()
    });
    const $newTarget = $target.clone()
    $newTarget.each((i, e) => {
        $(e).css({
            top: targetPositions[i].top,
            left: targetPositions[i].left,
            position: 'absolute',
            display: 'block',
            transition: AnimationTimeSec + 's all ease',
        });
    });

    // calculate new position
    const targetContents = $target.toArray()
        .sort(SortFunction)
        .map((a) => {
            return {
                html: a.innerHTML,
                href: a.href
            }
        });

    $target.each((i, e) => {
        const $e = $(e);
        $e.html(targetContents[i].html);
        $e.attr({href:targetContents[i].href});
    });

    const newPositions = {};
    $target.each((_, e) => {
        const $e = $(e)
        newPositions[$e.attr('href')] = $e.position();
    })

    // swap DOM
    $newTarget.appendTo($target.parent());
    $target.hide();

    // animation start
    setTimeout(() => {
        $newTarget.each((i, e) => {
            const $e = $(e);
            const pos = newPositions[$e.attr('href')];
            $e.css({
                top: pos.top,
                left: pos.left,
            });
        });
    }, 0);

    // complete animation restore DOM
    setTimeout(() => {
        $newTarget.remove();
        $target.show();
    }, (AnimationTimeSec + AnimationCompleteDelaySec) * 1000);
};
DomSortAnimation(
        $(".c-article-content .c-badge"),
        2,
        0.2,
        (a, b) => {
            const aContent = a.textContent.trim();
            const bContent = b.textContent.trim();
            if(aContent < bContent){
                return -1;
            }else if(aContent > bContent){
                return 1;
            }
            return 0;
        }
    );

前後の記事

Prev: