๐Ÿ”ฎ ์†Œ๋งˆ๋ฒ• ํ”„๋กœ์ ํŠธ -11 (music)

๋ฎค์ง ํ”Œ๋ ˆ์ด์–ด ์•ฑ์„ ๊ฐ„๋‹จํžˆ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค. audio DOM element๋ฅผ ์ฒ˜์Œ ๋‹ค๋ค„๋ณด์•˜๊ณ , class๋ฅผ ํ™œ์šฉํ•ด OOP๋„ ์‹ ๊ฒฝ์จ์„œ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.


#. Project Map


์ œ์ž‘๋…ธํŠธ ํ•œ๋ˆˆ์—๋ณด๊ธฐ[์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ]

1. Layout

1-1. ๋ฉ”์ธํ™”๋ฉด

music1

ํ™”๋ฉด ์ค‘์•™์—๋Š” Play/Stop ๋ฒ„ํŠผ๊ณผ ์–‘์˜†์œผ๋กœ๋Š” ์ด์ „๊ณก ๋ฒ„ํŠผ, ๋‹ค์Œ๊ณก ๋ฒ„ํŠผ์„ ๋‘์—ˆ๋‹ค.


1-2. ๐ŸŽต์Œ์•…์žฌ์ƒ์ค‘

music2

๊ทธ๋ฆฌ๊ณ  ์Œ์•…์ด ์žฌ์ƒ์ค‘์ผ ๋•Œ๋Š” ๋ฐฐ๊ฒฝ ๊ทธ๋ผ๋””์–ธํŠธ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ณด์ด๋„๋ก ํ•ด์ฃผ์—ˆ๋‹ค.


1-3. ์ปดํฌ๋„ŒํŠธ

<BackgroundContainer className="musicBackground" playToggle={playToggle}>
  <ContentsMenubar name="music" />
  <audio preload="auto" controls="none" style={{ display: 'none' }}></audio>
  <SideButton position="left" onClick={() => anotherMusicPlay('left')} />
  <PlayButton onClick={playMusic}>
    <div>{myMusic.name}</div>
    <p>{!playToggle ? `PLAY` : `STOP`}</p>
  </PlayButton>
  <SideButton position="right" onClick={() => anotherMusicPlay('right')} />
</BackgroundContainer>
Component Description
BackgroundContainer ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ Full Container styled-component
ContentsMenubar ๋ฉ”๋‰ด๋ฐ” import
audio ์Œ์•… ๊ด€๋ จ html element HTMLAudioElement
SideButton, PlayButton ๋ฒ„ํŠผ element styled-component

2. Sound Class

๋จผ์ € ์Œ์•…์„ ์ปจํŠธ๋กค ํ•ด์ค„ class๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.

export default class Sound {
  sound = null // HTMLAuioElement
  name = ''
  constructor(src) {
    this.sound = document.querySelector('audio')
    this.name = src
    this.sound.src = src
  }
  play() {
    this.sound.play()
  }
  stop() {
    this.sound.pause()
  }
}

state๋กœ๋Š” HTMLAudioElement์— ์ ‘๊ทผํ•˜๋Š” sound์™€ ์Œ์•…์ด๋ฆ„ name์„ ๋‘์—ˆ๊ณ , ๋ชจ๋“  ์Œ์•… ์ปจํŠธ๋กค์„ Sound๊ฐ์ฒด์—์„œ ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•ด์ฃผ์—ˆ๋‹ค.

3. Sound Control

3-1. Sound ๊ฐ์ฒด ์ƒ์„ฑ

const musics = ['1Upbeat Ukulele.mp3', '2Instrumental.mp3', '3Inspiration.mp3']

๋จผ์ € musics ๋ฐฐ์—ด์— ์Œ์•… ์ด๋ฆ„์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

useEffect(() => {
  setMyMusic(new Sound(musics[0]))
}, [])

๊ทธ๋ฆฌ๊ณ  ์ฒ˜์Œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋  ๋•Œ, Sound ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ƒ์„ฑ์ž ์ธ์ž๋กœ ์ฒซ๋ฒˆ์งธ ์Œ์•… (โ€˜1Upbeat Ukulele.mp3โ€™)์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ React Hooks์˜ useState๋ฅผ ์ด์šฉํ•ด์„œ MyMusic state์— ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.


3-2. ์Œ์•…์žฌ์ƒ

const playMusic = () => {
  !playToggle && myMusic.play()
  playToggle && myMusic.stop()
  setPlayToggle(!playToggle)
}

playMusic ํ•จ์ˆ˜๋Š” ๊ฐ€์šด๋ฐ Play/Stop ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜๋กœ, ํ˜„์žฌ playToggle ๊ฐ’์— ๋”ฐ๋ผ, myMusic ์˜ ๋ฉ”์†Œ๋“œ์— ์ ‘๊ทผํ•˜๊ฒŒ ํ•ด์ฃผ์—ˆ๋‹ค.


3-3. ๋‹ค์Œ๊ณก, ์ด์ „๊ณก

const anotherMusicPlay = direction => {
  myMusic.stop()
  if (direction === 'left') {
    return setMyMusic(
      new Sound(
        musics[
          (musics.indexOf(myMusic.name) + musics.length - 1) % musics.length
        ]
      )
    )
  }
  if (direction === 'right') {
    return setMyMusic(
      new Sound(musics[(musics.indexOf(myMusic.name) + 1) % musics.length])
    )
  }
}

anotherMusicPlay ํ•จ์ˆ˜๋Š” ์‚ฌ์ด๋“œ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์‹คํ–‰๋˜๋ฉฐ, direction์ด๋ผ๋Š” ์ธ์ž(์ด์ „๊ณก์ธ์ง€,๋‹ค์Œ๊ณก์ธ์ง€ ์ฒดํฌ)๋ฅผ ๋ฐ›๋Š”๋‹ค. ์šฐ์„  ํ˜„์žฌ myMusic ๊ฐ์ฒด์˜ ์Œ์•…์„ ๋ฉˆ์ถ”๊ณ  ์ธ์ž์— ๋”ฐ๋ฅธ ์กฐ๊ฑด๋ฌธ์„ ๋‘์—ˆ๋‹ค. ์กฐ๊ฑด๋ฌธ์€ else๋ฌธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  if์™€ return ๊ฐ’์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ฃผ์—ˆ๋‹ค.

useEffect(() => {
  playToggle && myMusic.play()
}, [myMusic])

๋‹ค์Œ๊ณก ํ˜น์€ ์ด์ „๊ณก์œผ๋กœ ๊ฐ์ฒด๊ฐ€ ๋ฐ”๋€Œ๋ฉด useEffect๋ฅผ ์ด์šฉํ•ด์„œ ์Œ์•…์„ ํ”Œ๋ ˆ์ดํ• ์ง€ ๊ณ„์† ์œ ์ง€ํ•  ๊ฒƒ์ธ์ง€ ์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€๋‹ค.

4. Background Animation

๋ฐฑ๊ทธ๋ผ์šด๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ styled-component ๋ฅผ ์ด์šฉํ–ˆ๊ณ , codepen.io์˜ ์†Œ์Šค๋ฅผ ์ฐธ๊ณ ํ•˜์˜€๋‹ค.

const rainbow = keyframes`
0%{background-position:0% 82%}
50%{background-position:100% 19%}
100%{background-position:0% 82%}
`

const BackgroundContainer = styled.div`
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  background: ${(props) => {
    return props.playToggle
      ? 'linear-gradient(124deg,#ff2400,#e81d1d,#e8b71d,#e3e81d,#1de840,#1ddde8,#2b1de8,#dd00f3,#dd00f3);'
      : 'white'
  }}
  background-size: 1800% 1800%;
  animation: ${rainbow} 18s ease infinite;

5. Feedback

5-1. Too simple

๋งŒ๋“ค๊ณ ๋‚˜๋‹ˆ ๋„ˆ๋ฌด ๊ฐ„๋‹จํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค์Œ์—๋Š” ์›น์—์„œ ํ•  ์ˆ˜ ์žˆ๋Š” ๋“œ๋Ÿผ์ด๋‚˜ ์‹ ๋””์‚ฌ์ด์ € ์–ดํ”Œ์ด๋‚˜ ์ •๋ง ๋ฎค์ง ํ”Œ๋ ˆ์ด์–ด ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ๋“ค์„ ๋„ฃ์–ด๋ณด๊ณ  ์‹ถ๋‹ค.


5-2. Animation

์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ธฐ๋Šฅ๋„ ๋ฒ„ํŠผ์— ๋ฐ˜์‘ํ•˜๋„๋ก ํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ ์ข€์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋˜์ง€ ์•Š์•˜๋‹ค. ์ค‘๋ณต ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์•ˆ๋˜๋Š” ๊ฒƒ ๊ฐ™๊ธฐ๋„ํ•˜๊ณ โ€ฆ ๋‹ค์Œ ์†Œ๋งˆ๋ฒ• ํ”„๋กœ์ ํŠธ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์— ๋Œ€ํ•ด์„œ ๋‹ค๋ค„๋ณผ๊นŒ ํ•œ๋‹ค.


Written by@taenyKim
๋ฐฐ์šฐ๋ฉฐ ์„ฑ์žฅํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ #FE #UI #๊ฐœ๋ฐœ #life

GitHubFacebook