ふわふわしたUIをつくろう

# はじめに
昔どこかのWebサイトで見た「ふわふわしたUI」を作ってみました。なんと呼べばいいのか分からないのでこのまま行きます。

# 完成図
[完成図はこちら](https://xn--28j4bvdyc.tech/blog/fuwafuwa/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778018/4f3cbbe8-c6a8-49e4-92f6-6de990cbb4c5.png)

# ソースコード
## HTML
### point
#fuwaAreaをふわふわの中心点を決めるために設定。
その中の.fuwaは独立したそれぞれの要素。今回は画像を入れていますが、文字などの要素も使えます。.fuwaの中なら複数の要素を入れても大丈夫です。
```html
<div id="fuwaArea">
        <div class="fuwa">
            <img src="https://icooon-mono.com/i/icon_11343/icon_113431_64.png">
        </div>
        <div class="fuwa">
            <img src="https://icooon-mono.com/i/icon_11343/icon_113431_64.png">
        </div>
        <div class="fuwa">
            <img src="https://icooon-mono.com/i/icon_11343/icon_113431_64.png">
        </div>
    </div>
```

## CSS
こちらは簡単に。
### point
今回のコードでは、エリアの中心に向かって移動するようにしかしていないため、エリアの形状は正方形が適しています。
```CSS
#fuwaArea {
    width: 400px;
    height: 400px;
    margin: 0 auto;
    position: relative;
    border: 2px dashed #ccc;
    border-radius: 50%;

}

.fuwa {
    position: absolute;
    cursor: grab;
}

.fuwa img {
    width: 64px;
    height: 64px;
}
```

## JavaScript
### point
複数の要素に対して同じように移動を定義しているのでループで書いちゃいます。オブジェクトごとに移動パラメータを変更したい場合は工夫してください。
```javascript
function animateFuwa() {
    const fuwaList = document.querySelectorAll('.fuwa');
    fuwaList.forEach(fuwa => {
        const fuwaArea = document.getElementById('fuwaArea');
        const fuwaAreaRect = fuwaArea.getBoundingClientRect();
        const fuwaAreaRectWidth = fuwaAreaRect.width;
        const fuwaAreaRectHeight = fuwaAreaRect.height;
        const fuwaAreaRectCenterX = fuwaAreaRect.left + fuwaAreaRectWidth / 2;
        const fuwaAreaRectCenterY = fuwaAreaRect.top + fuwaAreaRectHeight / 2;


        const rect = fuwa.getBoundingClientRect();
        const centerX = rect.left + rect.width / 2;
        const centerY = rect.top + rect.height / 2;

        const fuwaAreaRectCenterDistance = Math.sqrt(
            Math.pow(centerX - fuwaAreaRectCenterX, 2) +
            Math.pow(centerY - fuwaAreaRectCenterY, 2)
        );

        const fuwaAreaRectCenterAngle = Math.atan2(centerY - fuwaAreaRectCenterY, centerX - fuwaAreaRectCenterX);
        const fuwaAreaRectCenterSpeed = Math.pow(fuwaAreaRectCenterDistance, 2) / 10000;
        let moveX = - Math.cos(fuwaAreaRectCenterAngle) * fuwaAreaRectCenterSpeed;
        let moveY = - Math.sin(fuwaAreaRectCenterAngle) * fuwaAreaRectCenterSpeed;

        let otherFuwaList = Array.from(fuwaList).filter(obj => obj !== fuwa);

        otherFuwaList.forEach(otherFuwa => {
            const otherRect = otherFuwa.getBoundingClientRect();
            const otherX = otherRect.left + otherRect.width / 2;
            const otherY = otherRect.top + otherRect.height / 2;

            const dx = centerX - otherX;
            const dy = centerY - otherY;
            const angle = Math.atan2(dy, dx);
            const spanX = dx - fuwaAreaRectWidth / 2 - otherRect.width / 2;
            const spanY = dy - fuwaAreaRectHeight / 2 - otherRect.height / 2;
            const spanDistance = Math.sqrt(spanX * spanX + spanY * spanY);
            // 距離に応じてスピード調整（近いほど大きく動く）
            const speed = Math.pow(Math.max(1, 10 - spanDistance), 2) / 2;

            moveX += Math.cos(angle) * speed;
            moveY += Math.sin(angle) * speed;
        });

        // 現在の transform を取得して相対的に動かす
        const currentTransform = getComputedStyle(fuwa).transform;
        let currentX = 0, currentY = 0;

        if (currentTransform !== 'none') {
            const matrix = new DOMMatrix(currentTransform);
            currentX = matrix.m41;
            currentY = matrix.m42;
        }

        fuwa.style.transform = `translate(${currentX + moveX}px, ${currentY + moveY}px)`;
    });

    requestAnimationFrame(animateFuwa); // 続ける
}

// スタート
requestAnimationFrame(animateFuwa);
```

### 補遺
せっかく動くようにしたのに開始時だけじゃつまらない。ということで、手動でのドラッグをできるようにしてみましょう。
```javascript
document.querySelectorAll('.fuwa').forEach(enableDrag);
function enableDrag(element) {
    let isDragging = false;

    element.addEventListener('mousedown', (e) => {
        isDragging = true;
        e.preventDefault(); // 選択防止
    });

    document.addEventListener('mousemove', (e) => {
        if (isDragging) {
            const transform = getComputedStyle(element).transform;
            let currentX = 0, currentY = 0;

            if (transform !== 'none') {
                const matrix = new DOMMatrix(transform);
                currentX = matrix.m41;
                currentY = matrix.m42;
            }
            const rect = element.getBoundingClientRect();
            const offsetX = rect.width / 2;
            const offsetY = rect.height / 2;
            const fuwaArea = document.getElementById('fuwaArea');
            const fuwaAreaRect = fuwaArea.getBoundingClientRect();
            const x = e.pageX - offsetX - fuwaAreaRect.left;
            const y = e.pageY - offsetY - fuwaAreaRect.top;

            element.style.transform = `translate(${x}px, ${y}px)`;
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });
}
```
# 完成！！
これでふわふわしたUIを作成することができました。
ライブラリなどは使用していないため、このまま自由に使えるはずです
## 完成図(再掲)


# 問題点
* スマホ対応出来ていない
* 長時間開きっぱなしにしていると動作が重くなる(謎)




[完成図はこちら](https://xn--28j4bvdyc.tech/blog/fuwafuwa/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3778018/4f3cbbe8-c6a8-49e4-92f6-6de990cbb4c5.png)

### 修正
ドラッグ可能にする関数をanimationの中で呼び出していたので、1秒当たり60回のイベントリスナーがセットされていました。再帰の外でドラッグ可能にするように変更しました。