Vue-将HTML音频播放器转换为组件

时间:2019-01-17 14:11:24

标签: javascript vue.js vue-component

我正在将html中的播放器转换为Vue组件。

已经创建了一半的组件,只缺少时间控制滑块。

这是html播放器代码(带有多个标签的行已在Vue组件中实现):

var audioPlayer = document.querySelector('.green-audio-player');
var playPause = audioPlayer.querySelector('#playPause');
              var playpauseBtn = audioPlayer.querySelector('.play-pause-btn');
var loading = audioPlayer.querySelector('.loading');
var progress = audioPlayer.querySelector('.progress');
var sliders = audioPlayer.querySelectorAll('.slider');
var player = audioPlayer.querySelector('audio');
var currentTime = audioPlayer.querySelector('.current-time');
              var totalTime = audioPlayer.querySelector('.total-time');
var speaker = audioPlayer.querySelector('#speaker');

var draggableClasses = ['pin'];
var currentlyDragged = null;

window.addEventListener('mousedown', function(event) {
  
  if(!isDraggable(event.target)) return false;
  
  currentlyDragged = event.target;
  let handleMethod = currentlyDragged.dataset.method;
  
  this.addEventListener('mousemove', window[handleMethod], false);

  window.addEventListener('mouseup', () => {
    currentlyDragged = false;
    window.removeEventListener('mousemove', window[handleMethod], false);
  }, false);  
});

          playpauseBtn.addEventListener('click', togglePlay);
          player.addEventListener('timeupdate', updateProgress);
          player.addEventListener('loadedmetadata', () => {
            totalTime.textContent = formatTime(player.duration);
          });
          player.addEventListener('canplay', makePlay);
          player.addEventListener('ended', function(){
            playPause.attributes.d.value = "M18 12L0 24V0";
            player.currentTime = 0;
          });

sliders.forEach(slider => {
  let pin = slider.querySelector('.pin');
  slider.addEventListener('click', window[pin.dataset.method]);
});

function isDraggable(el) {
  let canDrag = false;
  let classes = Array.from(el.classList);
  draggableClasses.forEach(draggable => {
    if(classes.indexOf(draggable) !== -1)
      canDrag = true;
  })
  return canDrag;
}

function inRange(event) {
  let rangeBox = getRangeBox(event);
  let rect = rangeBox.getBoundingClientRect();
  let direction = rangeBox.dataset.direction;
  if(direction == 'horizontal') {
    var min = rangeBox.offsetLeft;
    var max = min + rangeBox.offsetWidth;   
    if(event.clientX < min || event.clientX > max) return false;
  } else {
    var min = rect.top;
    var max = min + rangeBox.offsetHeight; 
    if(event.clientY < min || event.clientY > max) return false;  
  }
  return true;
}

              function updateProgress() {
                var current = player.currentTime;
                var percent = (current / player.duration) * 100;
                progress.style.width = percent + '%';

                currentTime.textContent = formatTime(current);
              }

function getRangeBox(event) {
  let rangeBox = event.target;
  let el = currentlyDragged;
  if(event.type == 'click' && isDraggable(event.target)) {
    rangeBox = event.target.parentElement.parentElement;
  }
  if(event.type == 'mousemove') {
    rangeBox = el.parentElement.parentElement;
  }
  return rangeBox;
}

function getCoefficient(event) {
  let slider = getRangeBox(event);
  let rect = slider.getBoundingClientRect();
  let K = 0;
  if(slider.dataset.direction == 'horizontal') {
    
    let offsetX = event.clientX - slider.offsetLeft;
    let width = slider.clientWidth;
    K = offsetX / width;    
    
  } else if(slider.dataset.direction == 'vertical') {
    
    let height = slider.clientHeight;
    var offsetY = event.clientY - rect.top;
    K = 1 - offsetY / height;
    
  }
  return K;
}

function rewind(event) {
  if(inRange(event)) {
    player.currentTime = player.duration * getCoefficient(event);
  }
}

function formatTime(time) {
  var min = Math.floor(time / 60);
  var sec = Math.floor(time % 60);
  return min + ':' + ((sec<10) ? ('0' + sec) : sec);
}

              function togglePlay() {
                if(player.paused) {
                  playPause.attributes.d.value = "M0 0h6v24H0zM12 0h6v24h-6z";
                  player.play();
                } else {
                  playPause.attributes.d.value = "M18 12L0 24V0";
                  player.pause();
                }  
              }

              function makePlay() {
                playpauseBtn.style.display = 'block';
                loading.style.display = 'none';
              }
.audio.green-audio-player {
  width: 400px;
  min-width: 300px;
  height: 56px;
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.07);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 24px;
  padding-right: 24px;
  border-radius: 4px;
  user-select: none;
  -webkit-user-select: none;
  background-color: #fff;
}
.audio.green-audio-player .play-pause-btn {
  display: none;
  cursor: pointer;
}
.audio.green-audio-player .spinner {
  width: 18px;
  height: 18px;
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/loading.png);
  background-size: cover;
  background-repeat: no-repeat;
  animation: spin 0.4s linear infinite;
}
.audio.green-audio-player .slider {
  flex-grow: 1;
  background-color: #D8D8D8;
  cursor: pointer;
  position: relative;
}
.audio.green-audio-player .slider .progress {
  background-color: #44BFA3;
  border-radius: inherit;
  position: absolute;
  pointer-events: none;
}
.audio.green-audio-player .slider .progress .pin {
  height: 16px;
  width: 16px;
  border-radius: 8px;
  background-color: #44BFA3;
  position: absolute;
  pointer-events: all;
  box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.32);
}
.audio.green-audio-player .controls {
  font-family: 'Roboto', sans-serif;
  font-size: 16px;
  line-height: 18px;
  color: #55606E;
  display: flex;
  flex-grow: 1;
  justify-content: space-between;
  align-items: center;
  margin-left: 24px;
}
.audio.green-audio-player .controls .slider {
  margin-left: 16px;
  margin-right: 16px;
  border-radius: 2px;
  height: 4px;
}
.audio.green-audio-player .controls .slider .progress {
  width: 0;
  height: 100%;
}
.audio.green-audio-player .controls .slider .progress .pin {
  right: -8px;
  top: -6px;
}
.audio.green-audio-player .controls span {
  cursor: default;
}

svg, img {
  display: block;
}

@keyframes spin {
  from {
    transform: rotateZ(0);
  }
  to {
    transform: rotateZ(1turn);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="audio green-audio-player">
  <div class="loading">
    <div class="spinner"></div>
  </div>
  <div class="play-pause-btn">  
    <svg xmlns="http://www.w3.org/2000/svg" width="18" height="24" viewBox="0 0 18 24">
      <path fill="#566574" fill-rule="evenodd" d="M18 12L0 24V0" class="play-pause-icon" id="playPause"/>
    </svg>
  </div>
  
  <div class="controls">
    <span class="current-time">0:00</span>
    <div class="slider" data-direction="horizontal">
      <div class="progress">
        <div class="pin" id="progress-pin" data-method="rewind"></div>
      </div>
    </div>
    <span class="total-time">0:00</span>
  </div>
  
  <audio>
    <source src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/Swing_Jazz_Drum.mp3" type="audio/mpeg">
  </audio>
</div>

HTML Codepen:https://codepen.io/caiokawasaki/pen/JwVwry

这是Vue组件:

Vue.component('audio-player', {
  props: ['message'],
  data: () => ({
    audio: undefined,
    loaded: false,
    playing: false,
    currentTime: '00:00',
    totalTime: '00:00',
    percent: '0%',
    draggableClasses: ['pin'],
    currentlyDragged: null
  }),
  computed: {},
  methods: {
    formatTime(time) {
      var min = Math.floor(time / 60);
      var sec = Math.floor(time % 60);
      return min + ':' + ((sec < 10) ? ('0' + sec) : sec);
    },
    loadedMetaData() {
      this.totalTime = this.formatTime(this.audio.duration)
    },
    canPlay() {
      this.loaded = true
    },
    timeUpdate(){
      var current = this.audio.currentTime;
      var percent = (current / this.audio.duration) * 100;

      this.percent = percent + '%';

      this.currentTime = this.formatTime(current);
    },
    ended(){
      this.playing = false
      this.audio.currentTime = 0
    },
    isDraggable(el) {
      let canDrag = false;
      let classes = Array.from(el.classList);
      this.draggableClasses.forEach(draggable => {
        if (classes.indexOf(draggable) !== -1)
          canDrag = true;
      })
      return canDrag;
    },
    inRange(event) {
      let rangeBox = getRangeBox(event);
      let rect = rangeBox.getBoundingClientRect();
      let direction = rangeBox.dataset.direction;
      if (direction == 'horizontal') {
        var min = rangeBox.offsetLeft;
        var max = min + rangeBox.offsetWidth;
        if (event.clientX < min || event.clientX > max) return false;
      } else {
        var min = rect.top;
        var max = min + rangeBox.offsetHeight;
        if (event.clientY < min || event.clientY > max) return false;
      }
      return true;
    },
    togglePlay() {
      if (this.audio.paused) {
        this.audio.play();
        this.playing = true;
      } else {
        this.audio.pause();
        this.playing = false;
      }
    },
    makePlay() {
      playpauseBtn.style.display = 'block';
      loading.style.display = 'none';
    },
    getRangeBox(event) {
      let rangeBox = event.target;
      let el = currentlyDragged;
      if (event.type == 'click' && isDraggable(event.target)) {
        rangeBox = event.target.parentElement.parentElement;
      }
      if (event.type == 'mousemove') {
        rangeBox = el.parentElement.parentElement;
      }
      return rangeBox;
    },
    getCoefficient(event) {
      let slider = getRangeBox(event);
      let rect = slider.getBoundingClientRect();
      let K = 0;
      if (slider.dataset.direction == 'horizontal') {

        let offsetX = event.clientX - slider.offsetLeft;
        let width = slider.clientWidth;
        K = offsetX / width;

      } else if (slider.dataset.direction == 'vertical') {

        let height = slider.clientHeight;
        var offsetY = event.clientY - rect.top;
        K = 1 - offsetY / height;

      }
      return K;
    },
    rewind(event) {
      if (this.inRange(event)) {
        this.audio.currentTime = this.audio.duration * getCoefficient(event);
      }
    }
  },
  mounted() {
    this.audio = this.$refs.audio
  },
  template: `<div class="audio-message-content">
<a v-if="loaded" class="play-pause-btn" href="#" :title="playing ? 'Clique aqui para pausar o audio' : 'Clique aqui ouvir o audio'" @click.prevent="togglePlay">
<svg key="pause" v-if="playing" x="0px" y="0px" viewBox="0 0 18 20" style="width: 18px; height: 20px; margin-top: -10px">
<path d="M17.1,20c0.49,0,0.9-0.43,0.9-0.96V0.96C18,0.43,17.6,0,17.1,0h-5.39c-0.49,0-0.9,0.43-0.9,0.96v18.07c0,0.53,0.4,0.96,0.9,0.96H17.1z M17.1,20"/>
<path d="M6.29,20c0.49,0,0.9-0.43,0.9-0.96V0.96C7.19,0.43,6.78,0,6.29,0H0.9C0.4,0,0,0.43,0,0.96v18.07C0,19.57,0.4,20,0.9,20H6.29z M6.29,20"/>
</svg>
<svg key="play" v-else x="0px" y="0px" viewBox="0 0 18 22" style="width: 18px; height: 22px; margin-top: -11px">
<path d="M17.45,10.01L1.61,0.14c-0.65-0.4-1.46,0.11-1.46,0.91V20.8c0,0.81,0.81,1.32,1.46,0.91l15.84-9.87C18.1,11.43,18.1,10.41,17.45,10.01L17.45,10.01z M17.45,10.01"/>
</svg>
</a>
<div v-else class="loading">
<div class="spinner"></div>
</div>

<div class="controls">
<span class="current-time">{{ currentTime }}</span>
<div class="slider" data-direction="horizontal" @click="">
<div class="progress" :style="{width: percent}">
<div class="pin" id="progress-pin" data-method="rewind"></div>
</div>
</div>
<span class="total-time">{{ totalTime }}</span>
</div>

<audio ref="audio" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/Swing_Jazz_Drum.mp3" @loadedmetadata="loadedMetaData" @canplay="canPlay" @timeupdate="timeUpdate" @ended="ended"></audio>
</div>`
})

var app = new Vue({
  el: '#app'
})
.audio-message-content {
  width: 400px;
  min-width: 300px;
  height: 56px;
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.07);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 24px;
  padding-right: 24px;
  border-radius: 4px;
  user-select: none;
  -webkit-user-select: none;
  background-color: #fff;
}
.audio-message-content .play-pause-btn {
  position: relative;
  width: 18px;
  height: 22px;
  cursor: pointer;
}
.audio-message-content .play-pause-btn svg {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -9px;
}
.audio-message-content .spinner {
  width: 18px;
  height: 18px;
  background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/355309/loading.png);
  background-size: cover;
  background-repeat: no-repeat;
  animation: spin 0.4s linear infinite;
}
.audio-message-content .slider {
  flex-grow: 1;
  background-color: #D8D8D8;
  cursor: pointer;
  position: relative;
}
.audio-message-content .slider .progress {
  background-color: #44BFA3;
  border-radius: inherit;
  position: absolute;
  pointer-events: none;
}
.audio-message-content .slider .progress .pin {
  height: 16px;
  width: 16px;
  border-radius: 8px;
  background-color: #44BFA3;
  position: absolute;
  pointer-events: all;
  box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.32);
}
.audio-message-content .controls {
  font-family: 'Roboto', sans-serif;
  font-size: 16px;
  line-height: 18px;
  color: #55606E;
  display: flex;
  flex-grow: 1;
  justify-content: space-between;
  align-items: center;
  margin-left: 24px;
}
.audio-message-content .controls .slider {
  margin-left: 16px;
  margin-right: 16px;
  border-radius: 2px;
  height: 4px;
}
.audio-message-content .controls .slider .progress {
  width: 0;
  height: 100%;
}
.audio-message-content .controls .slider .progress .pin {
  right: -8px;
  top: -6px;
}
.audio-message-content .controls span {
  cursor: default;
}

svg, img {
  display: block;
}

@keyframes spin {
  from {
    transform: rotateZ(0);
  }
  to {
    transform: rotateZ(1turn);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <audio-player></audio-player>
</div>

Vue组件Codepen:https://codepen.io/caiokawasaki/pen/QzRMwz

以下功能我无法理解,也无法在互联网上找到任何东西:

window[handleMethod]
window[pin.dataset.method]

有人可以帮我完成这个组件吗?

编辑

我已将所有html和javascript转换为Vue组件,但无论如何仍无法正常工作。

唯一无法正常工作的是进度条。它需要执行两个功能:

  1. 单击它应该转到所需的时间。
  2. 单击图钉并拖动时,它应该转到所需的时间。

我使用Vue Cli,以上两个都不能以.vue文件的形式工作,但是在Codepen中通常只编写功能2。

Codepen:https://codepen.io/caiokawasaki/pen/VqOqBQ

1 个答案:

答案 0 :(得分:0)

通过从class B { @Asynchronous void b(@Observes(during = TransactionPhase.AFTER_SUCCESS) String msg) { .... do some db magic ... } } 元素的window[handleMethod]属性中派生方法的名称来执行函数data-

pin

所以<div class="pin" id="progress-pin" data-method="rewind"></div> 等同于window[handleMethod]

window.rewind()也是如此。

所以在您的情况下:

window[pin.dataset.method]

和:

this[handleMethod](event)

应该是合适的替代品。