Log

いろいろ

Reactではてなブログ記事に虹夏ちゃんをばらまく

このページに虹夏ちゃんをばらまきました。 動かすことができて、スワイプすると飛んでいきます。ダブルタップで増殖します。

以下のコードを記事に貼り付ければ虹夏ちゃんをばらまくことができます。ばらまきたくなったら使ってね。

<div id="root" style="position: relative;"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/interactjs/dist/interact.min.js"></script>
<script type="text/babel">
const Nijika=({item:t,addNijika:e,style:n})=>{let[o,i]=React.useState({x:0,y:0}),a=React.useRef(null);return React.useEffect(()=>{interact(a.current).draggable({inertia:!0,listeners:{move(t){i(e=>({x:e.x+=t.dx,y:e.y+=t.dy}))}}}).on("doubletap",e)},[]),<img ref={a}src={`https://cdn-ak.f.st-hatena.com/images/fotolife/m/mtzml/20230113/${t}.png`}style={{width:"200px",height:"200px",position:"absolute",zIndex:"9999",touchAction:"none",userSelect:"none",transform:`translate(${o.x}px, ${o.y}px)`,...n}}/>},randomInt=(t,e)=>Math.floor(Math.random()*(e-t+1)+t),randomPosition=()=>{let{top:t,left:e}=document.getElementById("root").getBoundingClientRect(),n=t+window.pageYOffset,o=e+window.pageXOffset,{offsetHeight:i,offsetWidth:a}=document.documentElement;return{top:`${randomInt(-1*n,i-n-200)}px`,left:`${randomInt(-1*o,a-o-200)}px`}},ITEM={DORITOS:"20230113014637",RIBBON:"20230113014654",PONDERING:"20230113014645"},initialNijikas=[{item:ITEM.DORITOS,style:randomPosition()},{item:ITEM.RIBBON,style:randomPosition()},{item:ITEM.PONDERING,style:randomPosition()}],App=()=>{let[t,e]=React.useState(initialNijikas),n=t=>{let n={item:t,style:randomPosition()};e(t=>[...t,n])};return t.map(t=><Nijika item={t.item}addNijika={()=>n(t.item)}style={t.style}/>)};ReactDOM.render(<App />,document.getElementById("root"));
</script>

Webアプリを埋め込む

先日の楽曲10選の冒頭。

2022年好きな楽曲10選 - Log

< デデッ!デデッ!デデッ!デッ!
テレレレレ~♪ >

これはdivタグを直接書いて右寄せなどを実現していました。HTMLが書けるということははてなブログ記事にWebアプリを埋め込むことができると思ってやってみたという経緯です。

CDNからReactなどのライブラリを読み込み、あとはscriptタグにJavaScriptを書くだけです。

<div id="root" style="position: relative;"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/interactjs/dist/interact.min.js"></script>
<script type="text/babel">
    <!-- ここに処理を書く -->
</script>

虹夏ちゃんを動かす、増やす、ばらまく

あいにくWebアプリを持ち合わせていなかったので作りました。Webアプリというほどでもないですが、Reactが動作することが確認できれば十分です。

まず、虹夏ちゃんを用意します。

よくばり虹夏セット

画像をドラッグできるようにするためにinteract.jsを利用します。

interact.js - JavaScript drag and drop, resizing and multi-touch gestures for modern browsers

過去にドラッグ&ドロップを実現するためにReact DnDを利用していましたが、こちらはmin.jsが1ファイルにまとまっていなかったため今回は見送りました。

ユビゲーム - Log

こんな感じでNijikaコンポーネントを実装しています。

const Nijika = ({ item, addNijika, style }) => {
  const [position, setPosition] = React.useState({ x: 0, y: 0 });
  const ref = React.useRef(null);

  React.useEffect(() => {
    interact(ref.current)
    .draggable({
      inertia: true,
      listeners: {
        move (e) {
          setPosition((oldPosition) => ({
            x: oldPosition.x += e.dx,
            y: oldPosition.y += e.dy
          }));
        }
      }
    })
    .on("doubletap", addNijika)
  }, []);

  return (
    <img
      ref={ref}
      src={`https://cdn-ak.f.st-hatena.com/images/fotolife/m/mtzml/20230113/${item}.png`}
      style={{
        width: "200px",
        height: "200px",
        position: "absolute",
        zIndex: "9999",
        touchAction: "none",
        userSelect: "none",
        transform: `translate(${position.x}px, ${position.y}px)`,
        ...style
      }}
    />
  );
}

useEffectでマウント時にinteract.jsを有効化します。inertia: trueを指定することで慣性がはたらき、虹夏ちゃんをスワイプすると吹っ飛ぶようになります。また、.on("doubletap", addNijika)によりダブルタップを検知し、虹夏ちゃんを増殖させます。

虹夏ちゃんの出現箇所はランダムです。propsとして受け取ったstyleにより配置されています。styleは以下のとおり計算。

const randomPosition = () => {
  // getBoundingClientRectはビューポートからの相対位置であるためスクロール量を足して絶対値を得る
  const { top, left } = document.getElementById("root").getBoundingClientRect();
  const rootTop = top + window.pageYOffset;
  const rootLeft = left + window.pageXOffset;

  const { offsetHeight, offsetWidth } = document.documentElement;

  return {
    // 画面からはみ出さないように要素幅200pxを引く
    top: `${randomInt(-1 * rootTop, offsetHeight - rootTop - 200)}px`,
    left: `${randomInt(-1 * rootLeft, offsetWidth - rootLeft - 200)}px`
  };
}

初期配置では画面からはみ出さないよう考慮していますが、虹夏ちゃんは画面外にも吹っ飛びます。横スクロールが発生してしまうので辞めてほしいですね。

虹夏ちゃんはルートのstateで管理します。

const ITEM = {
  DORITOS: "20230113014637",
  RIBBON: "20230113014654",
  PONDERING: "20230113014645"
};

const initialNijikas = [
  { item: ITEM.DORITOS, style: randomPosition() },
  { item: ITEM.RIBBON, style: randomPosition() },
  { item: ITEM.PONDERING, style: randomPosition() }
];

const App = () => {
  const [nijikas, setNijikas] = React.useState(initialNijikas);

  const addNijika = (item) => {
    const newNijika = { item, style: randomPosition() };
    setNijikas((oldNijikas) => [...oldNijikas, newNijika]);
  }

  return nijikas.map(nijika => (
    <Nijika
      item={nijika.item}
      addNijika={() => addNijika(nijika.item)}
      style={nijika.style}
    />
  ));
}  

ReactDOM.render(
  <App/>,
  document.getElementById("root")
);

ねくすと?

ドロップ検知を利用して、特定エリアに虹夏ちゃんを動かすると何かが起こるようにするとゲーム性があって面白いかもしれません。