Vue - 将 html 音频播放器转换为组件
Posted
技术标签:
【中文标题】Vue - 将 html 音频播放器转换为组件【英文标题】:Vue - Convert html audio player into component 【发布时间】:2019-06-11 18:17:00 【问题描述】:我正在将 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" 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 代码笔: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 组件代码笔:https://codepen.io/caiokawasaki/pen/QzRMwz
以下功能我无法理解,也无法在互联网上找到任何东西:
window[handleMethod]
window[pin.dataset.method]
谁能帮我完成这个组件?
编辑
我已将所有的 html 和 javascript 转换为 Vue 组件,但无论如何它仍然无法正常工作。
唯一不能正常工作的是进度条。它需要执行两个功能:
-
单击它应该会转到所需的时间。
单击图钉并将其拖动到所需时间。
我使用Vue Cli,以上两种都不是.vue
文件的形式,但是在Codepen中通常写的只有函数2有效。
Codepen:https://codepen.io/caiokawasaki/pen/VqOqBQ
【问题讨论】:
你终于完成了完全转换吗? 【参考方案1】:函数:window[handleMethod]
通过从pin
元素的data-
属性派生方法的名称来执行:
<div class="pin" id="progress-pin" data-method="rewind"></div>
所以window[handleMethod]
等价于window.rewind()
window[pin.dataset.method]
也是如此。
所以在你的情况下:
this[handleMethod](event)
和:
this[pin.dataset.method](event)
应该是合适的替代品。
【讨论】:
以上是关于Vue - 将 html 音频播放器转换为组件的主要内容,如果未能解决你的问题,请参考以下文章