質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

コンポーネントを描画するたびに、要素の位置をランダムに配置したい。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 479

whoiwhoi

score 27

動的に描画するコンポーネントにある要素を、描画するたびに違う位置に配置したいです。
下記はサンプルです。

サンプル

ランダムボタンを押すとランダムコンポーネントが描画されます。
ランダムコンポーネントにある赤ボタンにonMouseDownするとクリック中と表示し、onMouseUpするとクリック中の表示が消えます。
ランダムコンポーネントにあるトップボタンを押すと、ランダムコンポーネントをアンマウントします。

発生している問題

コンポーネントの描画毎に赤ボタンをランダムに配置できていますが、マウスイベントを実行するたびに、赤ボタンの位置が変更してしまいます。
マウスイベントでクリック中のstateを更新しているため、stateの更新による再レンダーで赤ボタンが移動しているかと思います。
stateの管理はHooksを利用しています。

実現したいこと

マウスイベントによって赤ボタンが移動することがないようにしたいです。
それを踏まえた上で、コンポーネントの描画毎に赤ボタンをランダムに配置したいです。

ソースコード

・Random.jsx

/** @jsx jsx */
import React, { useState } from "react";
import { jsx, css } from "@emotion/core";

const Random = ({ setRandom }) => {
  const click = () => {
    setRandom(false);
  };

  const [clicking, setClicking] = useState(false);
  const down = () => setClicking(true);
  const up = () => setClicking(false);

  const randomAreaWidth = window.screen.width;
  const randomAreaHight = window.screen.height - 100;
  let maxTop = randomAreaWidth * 0.3;
  let maxLeft = randomAreaHight * 0.2;
  const top = Math.random() * (maxTop + 1);
  const left = Math.random() * (maxLeft + 1);
  const button = css`
    background: red;
    border: none;
    color: white;
    cursor: pointer;
    width: 60px;
    height: 60px;
    transform: translate(${top}px, ${left}px);
  `;

  return (
    <React.Fragment>
      <header css={header}>
        <h2>ランダム</h2>
        <button onClick={click}>トップ</button>
        {clicking === true && <p>クリック中</p>}
      </header>
      <hr />
      <button css={button} onMouseDown={down} onMouseUp={up}>
        ボタン
      </button>
    </React.Fragment>
  );
};

const header = css`
  display: flex;
  align-items: center;
  height: 50px;
  h2,
  button {
    margin-right: 16px;
  }
`;

export default Random;

・App.jsx

/** @jsx jsx */
import React, { useState } from "react";
import { jsx } from "@emotion/core";
import Random from "./Random";

export default function App() {
  const [random, setRandom] = useState(false);
  const click = () => {
    setRandom(true);
  };

  return (
    <React.Fragment>
      <h1>トップ</h1>
      <button onClick={click}>ランダム</button>
      {random === true && <Random setRandom={setRandom} />}
    </React.Fragment>
  );
}

使用しているCSSフレームワーク

EmotionをCSS in JS記法で使用しています。

試したこと

1. 赤ボタンのCSSをランダムコンポーネントの外に移す
サンプル
マウスイベントのたびに赤ボタンが移動することは無くなりましたが、コンポーネントの描画毎にランダム配置ができず、毎回同じ位置に描画されます。
初回描画時のみランダムに配置できています。

2. クリック中かどうかをstateで管理しない
サンプル
再レンダーを防ぐためにクリック中かどうかをstateで管理せず、マウスダウンとアップのイベントそれぞれでtrueとfalseを変数に代入することで、マウスイベント毎の赤ボタンの移動は無くなりました。
しかし、クリック中メッセージの表示の切り替えができません。

3. 試したこと2 + クリック中メッセージの表示をCSSで切り替える
サンプル
クリック中メッセージの表示の切り替えを、

{clicking && <p>クリック中</p>}


上記のような論理 && 演算子によるインライン Ifではなく、下記のようにCSSで切り替える方向で試してみました。

const clickingMessage = css`
  display: ${clicking ? "block" : "none"};
`;


しかし2.と同じでクリック中かどうかをstateで管理していないため、displayの三項演算子は描画時しか実行されませんでした、

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

こんにちは、whoiwhoiさん

質問拝見しました。

ご認識の通り、Random.jsx内のCSSでtopleftのポジションを指定しているので、onMouseDownの時点で、Randomコンポーネントの再レンダリングが走り、その時点でtopleftが更新され、赤ボタンが移動することになります。

「ランダム」ボタンを押したときのみ、topleftの値を更新したいということは、親コンポーネントApp.jsxでポジションの値を保持して、「ランダム」ボタンを押したときにポジションの値を更新すると良さそうです。

コード例としては、以下になります

App.jsx

/** @jsx jsx */
import React, { useState } from "react";
import { jsx } from "@emotion/core";
import Random from "./Random";

export default function App() {
  const createPosition = () => {
    const randomAreaWidth = window.screen.width;
    const randomAreaHight = window.screen.height - 100;
    let maxTop = randomAreaWidth * 0.3;
    let maxLeft = randomAreaHight * 0.2;
    const top = Math.random() * (maxTop + 1);
    const left = Math.random() * (maxLeft + 1);

    return [top, left];
  };
  const [top, left] = createPosition();

  const [random, setRandom] = useState(false);
  const [position, setPosition] = useState({ top, left });

  const click = () => {
    const [top, left] = createPosition();
    setPosition({ top, left });
    setRandom(true);
  };

  return (
    <React.Fragment>
      <h1>トップ</h1>
      <button onClick={click}>ランダム</button>
      {random === true && <Random setRandom={setRandom} position={position} />}
    </React.Fragment>
  );
}

Random.jsx

/** @jsx jsx */
import React, { useState } from "react";
import { jsx, css } from "@emotion/core";

const Random = ({ setRandom, position }) => {
  const click = () => {
    setRandom(false);
  };

  const [clicking, setClicking] = useState(false);
  const down = () => setClicking(true);
  const up = () => setClicking(false);

  const { top, left } = position;
  const button = css`
    background: red;
    border: none;
    color: white;
    cursor: pointer;
    width: 60px;
    height: 60px;
    transform: translate(${top}px, ${left}px);
  `;

  return (
    <React.Fragment>
      <header css={header}>
        <h2>ランダム</h2>
        <button onClick={click}>トップ</button>
        {clicking === true && <p>クリック中</p>}
      </header>
      <hr />
      <button css={button} onMouseDown={down} onMouseUp={up}>
        ボタン
      </button>
    </React.Fragment>
  );
};

const header = css`
  display: flex;
  align-items: center;
  height: 50px;
  h2,
  button {
    margin-right: 16px;
  }
`;

export default Random;

以上、参考になれば幸いです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/10 09:35

    ご回答いただきありがとうござました。希望通りの挙動になりました。再レンダリングの影響を受けさせたくないものを親へ移すということですね。とても参考になりました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る