我怎样才能创建一个JavaScript动画来识别它何时击中物体,换句话说,一个击中框?

问题描述 投票:0回答:1

我目前正在用 React 构建一架带有可视化工具的钢琴。用户可以录制他们的歌曲并播放。该录音记录了笔记列表+开始/结束时间戳。我目前有从屏幕顶部开始并扩展结束时间戳 - 开始时间戳的高度的注释可视化。然后注释从顶部落下,直到超出屏幕并被删除。这两者都是使用带有前向填充的动画来完成的。

我的问题是:当可视化按下按键时,我需要播放音符声音,而目前,基于超时的这一点给我带来了问题。对我来说,将这种声音基于击中关键 div 的动画可视化更有意义,但我不知道如何做到这一点。

此外,存储动画以便能够暂停和恢复给我带来了很多问题,我想知道是否有更具体的方法来做到这一点。暂停和恢复动画确实很挑剔。我不想将其另存为视频。

Piano

动画

export const attribute_animation = (object, attribute, start, end, duration, easing) => {
    return object.animate(
        [{[attribute]: start}, { [attribute]: end}],
        {duration: duration, fill: 'forwards', easing: easing}
    )
}

我首先对高度属性进行动画处理,然后对顶部属性进行动画处理。如果某个特定的 div 碰到了另一个 div,如何通过 ref 识别它? (可视化击键)

钢琴组件

  let [playing, set_playing] = useState(null)
  let start_note_index = useRef(0)
  let end_note_index = useRef(0)
  let song_player = useRef(null)
  let sp_start_time = useRef(null)
  let sp_anim_frameid = useRef(null)
function song_playback() {
  
      function animate(timestamp) {
        if (!sp_start_time.current) {
          sp_start_time.current = timestamp
        }
        if (start_note_index.current < song.length && 
          song[start_note_index.current]['note']['start_timestamp'] <= timestamp - sp_start_time.current) {
          
          let pkey_index = pkey_to_pkeyind[song[start_note_index.current]['note']['note']]
          set_piano(prev_state => {
            const keys = [...prev_state]
            keys[pkey_index] = React.cloneElement(keys[pkey_index], { pb_visual_mode: 'expand_down' })
            return keys
          })
          start_note_index.current += 1
        }
        if (end_note_index.current < song.length && 
          song[end_note_index.current]['note']['end_timestamp'] <= timestamp - sp_start_time.current) {
          
          let pkey_index = pkey_to_pkeyind[song[end_note_index.current]['note']['note']]
          set_piano(prev_state => {
            const keys = [...prev_state]
            keys[pkey_index] = React.cloneElement(keys[pkey_index], { pb_visual_mode: 'move_down' })
            return keys
          })
          end_note_index.current += 1
          }
          if (start_note_index.current >= song.length && end_note_index.current >= song.length) {
            set_playing(null)
            cancelAnimationFrame(sp_anim_frameid.current)
          } else {
            if (playing === 'playing') {
              sp_anim_frameid.current = requestAnimationFrame(animate)
            }
          }
        
      }
    
      function pause() {
        cancelAnimationFrame(sp_anim_frameid.current)
        set_piano(prev_state => {
          const keys = [...prev_state]
          for (let i = 0; i < keys.length; i++) {
            keys[i] = React.cloneElement(keys[i], { pb_visual_mode: 'pause' })
          }
          return keys
        })
      }
    
      function resume() {
        requestAnimationFrame(animate)
        set_piano(prev_state => {
          const keys = [...prev_state]
          for (let i = 0; i < keys.length; i++) {
            keys[i] = React.cloneElement(keys[i], { pb_visual_mode: 'resume' })
          }
          return keys
        })
      }
    
      function reset() {
        sp_start_time.current = null
        start_note_index.current = 0
        end_note_index.current = 0
      }

      return { pause, resume, reset }
    }
    song_player.current = song_playback()
  }, [song, playing])

钢琴琴键部件

  let [playback_visuals, set_playback_visuals] = useState([])
  let pb_counter = useRef(0)
  let curr_pb_anim = useRef([[], true])
  let playback_visual_refs = useRef({})
  let timeouts = useRef({})
  let timeouts_counter = useRef(0)
useEffect(() => {
    if (playback_visuals[pb_counter.current] && curr_pb_anim.current[1]) {
      curr_pb_anim.current[0].push(attribute_animation(playback_visual_refs.current[pb_counter.current], 'height', '0', '300000px', 1000000))
      curr_pb_anim.current[1] = false
    }
    
  }, [playback_visuals])

  useEffect(() => {
    if (pb_visual_mode === 'expand_down') {

      let curr_counter = timeouts_counter.current
      const timeout_id = new Timer((curr_counter) => {
        audio.current.play()
        delete timeouts.current[curr_counter]
      }, 2000, curr_counter)

      timeouts.current[timeouts_counter.current] = timeout_id
      timeouts_counter.current += 1

      set_playback_visuals(prev_state => {
        curr_pb_anim.current[1] = true

        return ({...prev_state,
          [pb_counter.current]: (
          <div key={`${pb_counter.current}`} ref={ref => playback_visual_refs.current[pb_counter.current] = ref} className='pb-visualizer-instance'></div>
          )
        })
      })
    } else if (pb_visual_mode === 'move_down' && curr_pb_anim.current[0]) {

      console.log(curr_pb_anim.current, note)
      curr_pb_anim.current[0][curr_pb_anim.current[0].length-1].pause()
      
      curr_pb_anim.current[0][curr_pb_anim.current[0].length-1] = attribute_animation(playback_visual_refs.current[pb_counter.current], 'top', '0', '300000px', 1000000, 'linear')
      curr_pb_anim.current[1] = true


      let curr_t_counter = timeouts_counter.current
      let curr_pb_counter = pb_counter.current
      const timeout_id = new Timer((curr_t_counter, curr_pb_counter) => {
        delete timeouts.current[curr_t_counter]
        delete playback_visual_refs.current[curr_pb_counter]
        delete curr_pb_anim.current[curr_pb_counter]
        set_playback_visuals(prev_state => {
          const new_state = Object.keys(prev_state).filter(key => key !== curr_pb_counter).reduce((acc, key) => {
            acc[key] = prev_state[key]
            return acc
          }, {})
          return new_state
        })
      }, 3000, curr_t_counter, curr_pb_counter)

      timeouts.current[timeouts_counter.current] = timeout_id
      timeouts_counter.current += 1

      pb_counter.current += 1

    } else if (pb_visual_mode === 'pause') {
      
      for (let i = 0; i < curr_pb_anim.current[0].length; i++) {
        console.log("pause", curr_pb_anim.current, note)
        curr_pb_anim.current[0][i].pause()
      }
    
      for (let timer_key in timeouts.current) {
        timeouts.current[timer_key].pause()
      }

      
      
    } else if (pb_visual_mode == 'resume') {
      for (let i = 0; i < curr_pb_anim.current[0].length; i++) {
        console.log("resume", curr_pb_anim.current, note)
        curr_pb_anim.current[0][i].play()
      }

      for (let timer_key in timeouts.current) {
        timeouts.current[timer_key].resume()
      }
    }
  }, [pb_visual_mode])
javascript reactjs state
1个回答
0
投票

我尝试了观察者 API 并将 rootMargin 设置为与钢琴键对齐,但无法使其工作。我最终所做的只是进行数学计算以随窗口高度缩放。这是我的代码,以防有人最终编写这样的代码:

useEffect(() => {
    
    if (playback_visuals[pb_counter.current] && curr_pb_anim.current[1]) {
      const computed_style = window.getComputedStyle(playback_visual_refs.current[pb_counter.current])
      const height = parseFloat(computed_style.getPropertyValue('height'))
      console.log(key_wrapper.current.clientHeight)
      const duration = 3000 + ((key_wrapper.current.clientHeight / 300) * height)

      console.log(duration)
      const f_timeout_id = new Timer(() => {
        audio.current.play()
      }, (3000 * .75))

      let curr_pb_counter = pb_counter.current
      let curr_timeout_counter = timeouts_counter.current
      const s_timeout_id = new Timer((curr_pb_counter, curr_timeout_counter) => {
        set_playback_visuals(prev_state => {
          const new_state = Object.keys(prev_state).filter(key => key !== curr_pb_counter).reduce((acc, key) => {
            acc[key] = prev_state[key]
            return acc
          }, {})
          return new_state
        })
        delete timeouts.current[curr_timeout_counter]

        if (end_song !== null) {
          end_song(null)
        }
      }, 3000, curr_pb_counter, curr_timeout_counter)

      timeouts.current[timeouts_counter.current] = f_timeout_id
      timeouts_counter.current += 1
      timeouts.current[timeouts_counter.current] = s_timeout_id
      timeouts_counter.current += 1

      console.log(.75 * key_wrapper.current.clientHeight / duration)
      curr_pb_anim.current[0].push(attribute_animation(playback_visual_refs.current[pb_counter.current], 'top', 
      `-${height}px`,
       `${(key_wrapper.current.clientHeight * .75)}px`, duration))
      curr_pb_anim.current[1] = false
    }
    
  }, [playback_visuals])
© www.soinside.com 2019 - 2024. All rights reserved.