LoginSignup
6
6

More than 3 years have passed since last update.

Reactでアプリっぽい美ページを作りたい

Last updated at Posted at 2019-11-17

できたもの

好きなアーティストの曲をSpotifyからとってきて、BPM(曲の速さ)順に表示し1、試聴させてくれます。
スマホ前提。

https://spotify-bpm.now.sh/
https://github.com/IYA-UFO/spotify-bpm

spotify-bpm.gif

背景

本業はいわゆるマークアップエンジニア。
完全に静的なランディングページを1文字1文字ゴリゴリ :pick::pick::pick:コーディングしたりしている。

しかしなぜかReactで作っている。

Reactを使うからには、色々操作できて、データとか取得して、ヌルヌル動く、SPAっぽい...

..いや、ネイティブアプリっぽい美ページを作ってみたい!!

できたこと

横スクロール

アーティスト選択と曲選択の行き来は横スクロール!
100%原始的ハンドメイド。


const Index = () => (
  <ThemeProvider theme={{ isSongView }}>
    <Row>
      <LeftCol>アーティスト選択</LeftCol>
      <RightCol>曲選択</RightCol>
    </Row>
  </ThemeProvider>
 );

const Row = styled.div`
  width: 200%;
  transform: ${({ theme }) =>
    theme.isSongView ? 'translateX(-50%)' : 'translateX(0)'};
  transition: transform 0.5s;
  display: flex;
  flex-direction: row;
`;

const LeftCol = styled.div`
  width: 50%;
  height: ${({ theme }) => (theme.isSongView ? '100%' : 'auto')};
`;

const RightCol = styled.div`
  width: 50%;
  height: ${({ theme }) => (theme.isSongView ? 'auto' : '100%')};
`;

非表示画面の高さを100%固定に直さないと、表示されていない画面の高さに応じてスクロールできてしまうのがポイント。

試聴コントローラーを下に固定

player.png

position:fixed で一発!と思ったら気のせいだった。
画面高さを100vhにしているのだが、Safariではここにメニューバー?の高さが含まれている。

スクリーンショット 2019-11-17 16.52.28.png

そのため、

.player {
  position:fixed;
  bottom:0;
}

ではコントローラーはメニューバーの下に隠れてしまう。。。

なので、マウント後にwindow.innerHeightを測って表示領域の高さを直した。
viewportとは(哲学)

const SongPicker = () => {
  const [innerHeight, setInnerHeight] = useState(0);
  useEffect(() => {
    setInnerHeight(window.innerHeight);
  }, []);

  return (
    <Wrap innerHeight={innerHeight}>
      <p>コンテンツ</p>
    </Wrap>
  );
};

const Wrap = styled.div`
  height: ${({ innerHeight }) => `${innerHeight}px`};
`;

export default SongPicker;

試聴の開始・終了でフワッとアニメーションを消す

wave.png

この波アニメーションは重く、再生中以外の曲では透過なので隠すのではなく要素を削除する必要がある。

オーディオが再生されたらフワッと出すのは簡単だ。
停止したらフワッと消すのは...?

例えば↓のようにすると、再生が終わった瞬間ブツ!っと消えてしまう。

const Wave = ({ isPlaying }) => (
  <Wrap>{isPlaying && <p>波のアニメーション色々</p>}</Wrap>
);

↓のように、onAnimationEndというイベントでレンダリングを止めるようにした波要素なら、自身のアニメーションが終わってから人知れず静かに消えてくれる。

参考: https://czaplinski.io/blog/super-easy-animation-with-react-hooks/

const Wave = ({ isPlaying }) => {
  const [shouldRender, setRender] = useState(isPlaying);

  useEffect(() => {
    if (isPlaying) setRender(true);
  }, [isPlaying]);

  const onAnimationEnd = () => {
    if (!isPlaying) {
      setRender(false);
    }
  };
  return (
    <Wrap isPlaying={isPlaying} onAnimationEnd={onAnimationEnd} >
      {shouldRender && <p>波のアニメーション色々</p>}
    </Wrap>
  );
};

曲一覧のロード中にかっこいいプレースホルダー

上から順に ふわ~っと 点滅する表示枠みたいなアレである。

loading.gif

各行に1-15の番号を渡してやり、それぞれ自分の番号×0.1秒遅れて点滅アニメーションを始める。

const LoadingAnimation = () => {
  const loadingArray = [];
  for (let i = 0; i < 15; i++) {
    loadingArray.push('');
  }
  return (
    <>
      {loadingArray.map((el, index) => (
        <Row key={index} index={index} />
      ))}
    </>
  );
};

const blink = keyframes`
  0% { opacity: 0; }
  50% { opacity: 1;}
  100% {opacity: 0;}
`;

const Row = styled.div`
  &:nth-of-type(2n) {
    opacity: 0;
    animation: ${blink} 2s linear ${({ index }) => `0.${index}s`} infinite;
  }
`;

15なのはスマホの画面を埋め尽くして余裕があるから。
動けばよかろうだ。

できなかったけど やりたいこと

Spotifyアクセストークンのキャッシュ

SpotifyのAPIに曲やアーティストの情報を教えてもらうには、
- Spotify for Developer でIDとパスを発行する
- ID,パスを添えて「アクセストークン」をリクエストする
- アクセストークンを添えて本当に欲しかった情報をリクエスト

という2段階になっている!
サーバー側の知識が≒0な自分にはわからない深い事情があるに違いない。

IDとパスワードをクライアントに配るのもどうかと思い、Spotifyからの情報取得はNext.jsを経由してもらうことにした。

構成.jpg

すると、アーティストの一覧を検索するだけで

  1. Webページを表示
  2. クライアント⇨アーティスト情報を要求⇨Next.js
  3. Next.js⇨トークンを要求⇨Spotify
  4. Spotify⇨トークンを返却⇨Next.js
  5. Next.js⇨アーティスト情報を要求⇨Spotify
  6. Spotify⇨アーティスト情報を返却⇨Next.js
  7. Next.js⇨アーティスト情報を返却⇨クライアント

と長い長い道のり。
タイムラグがすごい。

「Next.jsがアクセストークンをしばらく記憶」すれば無駄↓が減るが、
スクリーンショット 2019-11-17 17.50.39.png

自分にとってサーバーとは、URLに応じて何度でも同じものを返す記憶喪失マシンだったので、サーバーが記憶?!を持つなんて:scream: と脳が停止して挫折した2

が、速さを求めるなら避けられない

宣言的なアニメーション

アニメーションを手探りで頑張った結果、気づくと「あれしたら、次にこれして...」みたいな手続き的?なコードだらけになっていた。

  //該当部分例
  const handleBackClick = () => {
    goBackToAirtistPicker();
    audioRef.current.src = '';
    coverRef.current.scrollTo({
      top: 0,
      behavior: 'smooth',
    });
  };

何か別世界の香りがする...
jquery-761x437.jpg

Reactらしく宣言的なアニメーションの書き方を先人たちが開拓しているはずである。学ぼう。

説明ページ

ほぼ自習用とは言え、使い方を説明すべきである。ひょっとすると検索エンジンにかかるようになるかもしれない。

横スクロール時をエレガントに

横スクロールでアプリっぽい!と喜ぶには以下の2つがほしい
- 曲選択からアーティスト選択に戻るとき、アニメーション終了まで曲選択画面の内容を消さない
- 3ページ目以降もスイスイ足せる、自然で拡張性のある書き方 :map:


  1. 趣味でドラムパターンを考えることがあり、自分の曲と似たテンポの曲を聴きたいことがよくある 

  2. ... 友人から lru-cacheを教えてもらったが動かせなかった 

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6