垃圾vite 换成webpack
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div
|
||||
class="aplayer-bar-wrap"
|
||||
@mousedown="onThumbMouseDown"
|
||||
@touchstart="onThumbTouchStart"
|
||||
ref="barWrap"
|
||||
>
|
||||
<div class="aplayer-bar">
|
||||
<div
|
||||
class="aplayer-loaded"
|
||||
:style="{width: `${loadProgress * 100}%`}">
|
||||
</div>
|
||||
<div
|
||||
class="aplayer-played"
|
||||
:style="{width: `${playProgress * 100}%`, background: theme}"
|
||||
>
|
||||
<span
|
||||
ref="thumb"
|
||||
@mouseover="thumbHovered = true"
|
||||
@mouseout="thumbHovered = false"
|
||||
class="aplayer-thumb"
|
||||
:style="{borderColor: theme, backgroundColor: thumbHovered ? theme : '#fff'}"
|
||||
>
|
||||
<span class="aplayer-loading-icon"
|
||||
:style="{backgroundColor: theme }"
|
||||
>
|
||||
<icon type="loading"/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getElementViewLeft} from '../utils'
|
||||
import Icon from './aplayer-icon.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon
|
||||
},
|
||||
props: ['loadProgress', 'playProgress', 'theme'],
|
||||
data () {
|
||||
return {
|
||||
thumbHovered: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onThumbMouseDown (e) {
|
||||
const barWidth = this.$refs.barWrap.clientWidth
|
||||
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
|
||||
this.$emit('dragbegin', percentage)
|
||||
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||
document.addEventListener('mouseup', this.onDocumentMouseUp)
|
||||
},
|
||||
onDocumentMouseMove (e) {
|
||||
const barWidth = this.$refs.barWrap.clientWidth
|
||||
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
|
||||
this.$emit('dragging', percentage)
|
||||
},
|
||||
onDocumentMouseUp (e) {
|
||||
document.removeEventListener('mouseup', this.onDocumentMouseUp)
|
||||
document.removeEventListener('mousemove', this.onDocumentMouseMove)
|
||||
|
||||
const barWidth = this.$refs.barWrap.clientWidth
|
||||
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
this.$emit('dragend', percentage)
|
||||
},
|
||||
onThumbTouchStart (e) {
|
||||
const barWidth = this.$refs.barWrap.clientWidth
|
||||
let percentage = (e.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
|
||||
this.$emit('dragbegin', percentage)
|
||||
document.addEventListener('touchmove', this.onDocumentTouchMove)
|
||||
document.addEventListener('touchend', this.onDocumentTouchEnd)
|
||||
},
|
||||
onDocumentTouchMove (e) {
|
||||
const touch = e.changedTouches[0]
|
||||
const barWidth = this.$refs.barWrap.clientWidth
|
||||
let percentage = (touch.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
|
||||
this.$emit('dragging', percentage)
|
||||
},
|
||||
onDocumentTouchEnd (e) {
|
||||
document.removeEventListener('touchend', this.onDocumentTouchEnd)
|
||||
document.removeEventListener('touchmove', this.onDocumentTouchMove)
|
||||
|
||||
const touch = e.changedTouches[0]
|
||||
const barWidth = this.$refs.barWrap.clientWidth
|
||||
let percentage = (touch.clientX - getElementViewLeft(this.$refs.barWrap)) / barWidth
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
this.$emit('dragend', percentage)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
||||
.aplayer-bar-wrap {
|
||||
margin: 0 0 0 5px;
|
||||
padding: 4px 0;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
|
||||
.aplayer-bar {
|
||||
position: relative;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background: #cdcdcd;
|
||||
|
||||
.aplayer-loaded {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: #aaa;
|
||||
height: 2px;
|
||||
transition: all 0.5s ease;
|
||||
|
||||
will-change: width;
|
||||
}
|
||||
|
||||
.aplayer-played {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
transition: background-color .3s;
|
||||
will-change: width;
|
||||
|
||||
.aplayer-thumb {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 5px;
|
||||
margin-top: -5px;
|
||||
margin-right: -10px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1px solid;
|
||||
transform: scale(.8);
|
||||
will-change: transform;
|
||||
transition: transform 300ms, background-color .3s, border-color .3s;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
.aplayer-loading-icon {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
animation: spin 1s linear infinite;
|
||||
fill: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aplayer-loading {
|
||||
.aplayer-bar-wrap .aplayer-bar .aplayer-thumb .aplayer-loading-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played .aplayer-thumb {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="aplayer-volume-wrap">
|
||||
<icon-button
|
||||
:class="`aplayer-icon-${volumeIcon}`"
|
||||
:icon="volumeIcon"
|
||||
@click="$emit('togglemute')"
|
||||
/>
|
||||
<div
|
||||
class="aplayer-volume-bar-wrap"
|
||||
@mousedown="onBarMouseDown"
|
||||
>
|
||||
<div class="aplayer-volume-bar" ref="bar">
|
||||
<div
|
||||
class="aplayer-volume"
|
||||
:style="{
|
||||
height: muted ? 0 : `${Math.trunc(volume * 100)}%`,
|
||||
background: theme
|
||||
}"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './aplayer-iconbutton.vue'
|
||||
import {getElementViewTop} from '../utils'
|
||||
|
||||
const barHeight = 40
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
props: ['volume', 'muted', 'theme'],
|
||||
computed: {
|
||||
volumeIcon () {
|
||||
if (this.muted || this.volume <= 0) return 'volume_off'
|
||||
if (this.volume >= 1) return 'volume_up'
|
||||
return 'volume_down'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
adjustVolume (e) {
|
||||
let percentage = (barHeight - e.clientY + getElementViewTop(this.$refs.bar)) / barHeight
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
this.$emit('setvolume', percentage)
|
||||
},
|
||||
onBarMouseDown () {
|
||||
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||
document.addEventListener('mouseup', this.onDocumentMouseUp)
|
||||
},
|
||||
onDocumentMouseMove (e) {
|
||||
let percentage = (barHeight - e.clientY + getElementViewTop(this.$refs.bar)) / barHeight
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
this.$emit('setvolume', percentage)
|
||||
},
|
||||
onDocumentMouseUp (e) {
|
||||
document.removeEventListener('mouseup', this.onDocumentMouseUp)
|
||||
document.removeEventListener('mousemove', this.onDocumentMouseMove)
|
||||
|
||||
let percentage = (barHeight - e.clientY + getElementViewTop(this.$refs.bar)) / barHeight
|
||||
percentage = percentage > 0 ? percentage : 0
|
||||
percentage = percentage < 1 ? percentage : 1
|
||||
this.$emit('setvolume', percentage)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.aplayer-volume-wrap {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
z-index: 0;
|
||||
|
||||
&:hover .aplayer-volume-bar-wrap {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aplayer-volume-bar-wrap {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
height: 40px;
|
||||
z-index: -1;
|
||||
transition: all .2s ease;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -16px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 62px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.07), 0 0 5px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.aplayer-volume-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 11px;
|
||||
width: 5px;
|
||||
height: 40px;
|
||||
background: #aaa;
|
||||
border-radius: 2.5px;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.aplayer-volume {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition: height 0.1s ease, background-color .3s;
|
||||
will-change: height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div class="aplayer-controller">
|
||||
<v-progress
|
||||
:loadProgress="loadProgress"
|
||||
:playProgress="playProgress"
|
||||
:theme="theme"
|
||||
@dragbegin="val => $emit('dragbegin', val)"
|
||||
@dragend="val => $emit('dragend', val)"
|
||||
@dragging="val => $emit('dragging', val)"
|
||||
/>
|
||||
<div class="aplayer-time">
|
||||
<div class="aplayer-time-inner">
|
||||
- <span class="aplayer-ptime">{{secondToTime(stat.playedTime)}}</span> / <span
|
||||
class="aplayer-dtime">{{secondToTime(stat.duration)}}</span>
|
||||
</div>
|
||||
<volume
|
||||
v-if="!$parent.isMobile"
|
||||
:volume="volume"
|
||||
:theme="theme"
|
||||
:muted="muted"
|
||||
@togglemute="$emit('togglemute')"
|
||||
@setvolume="v => $emit('setvolume', v)"
|
||||
/>
|
||||
<icon-button
|
||||
class="aplayer-icon-mode"
|
||||
icon="shuffle"
|
||||
:class="{ 'inactive': !shuffle }"
|
||||
@click="$emit('toggleshuffle')"
|
||||
/>
|
||||
<icon-button
|
||||
class="aplayer-icon-mode"
|
||||
:icon="repeat === 'repeat_one' ? 'repeat_one' : 'repeat_all'"
|
||||
:class="{ 'inactive': repeat === 'no_repeat'}"
|
||||
@click="$emit('nextmode')"
|
||||
/>
|
||||
<icon-button
|
||||
class="aplayer-icon-menu"
|
||||
icon="menu"
|
||||
:class="{ 'inactive': !$parent.showList }"
|
||||
@click="$emit('togglelist')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconButton from './aplayer-iconbutton.vue'
|
||||
import VProgress from './aplayer-controller-progress.vue'
|
||||
import Volume from './aplayer-controller-volume.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconButton,
|
||||
VProgress,
|
||||
Volume,
|
||||
},
|
||||
props: ['shuffle', 'repeat', 'stat', 'theme', 'volume', 'muted'],
|
||||
computed: {
|
||||
loadProgress () {
|
||||
if (this.stat.duration === 0) return 0
|
||||
return this.stat.loadedTime / this.stat.duration
|
||||
},
|
||||
playProgress () {
|
||||
if (this.stat.duration === 0) return 0
|
||||
return this.stat.playedTime / this.stat.duration
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
secondToTime (second) {
|
||||
if (isNaN(second)) {
|
||||
return '00:00'
|
||||
}
|
||||
const pad0 = (num) => {
|
||||
return num < 10 ? '0' + num : '' + num
|
||||
}
|
||||
|
||||
const min = Math.trunc(second / 60)
|
||||
const sec = Math.trunc(second - min * 60)
|
||||
const hours = Math.trunc(min / 60)
|
||||
const minAdjust = Math.trunc((second / 60) - (60 * Math.trunc((second / 60) / 60)))
|
||||
return second >= 3600 ? pad0(hours) + ':' + pad0(minAdjust) + ':' + pad0(sec) : pad0(min) + ':' + pad0(sec)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.aplayer-controller {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.aplayer-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
height: 17px;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
padding-left: 7px;
|
||||
|
||||
.aplayer-volume-wrap {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.aplayer-icon {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-left: 4px;
|
||||
|
||||
&.inactive {
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.aplayer-fill {
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.aplayer-fill {
|
||||
fill: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&.aplayer-icon-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.aplayer-volume-wrap + .aplayer-icon {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&.aplayer-time-narrow {
|
||||
.aplayer-icon-mode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aplayer-icon-menu {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<img :src="svg" :style="style" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const requireAssets = require.context('../assets', false, /\.svg$/)
|
||||
const SVGs = requireAssets.keys().reduce((svgs, path) => {
|
||||
const svgFile = requireAssets(path)
|
||||
svgs[path.match(/^.*\/(.+?)\.svg$/)[1]] = svgFile
|
||||
return svgs
|
||||
}, {})
|
||||
export default {
|
||||
props: ['type'],
|
||||
computed: {
|
||||
svg () {
|
||||
return SVGs[this.type] || {}
|
||||
},
|
||||
style () {
|
||||
if (this.type === 'next') {
|
||||
return {
|
||||
transform: 'rotate(180deg)',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="aplayer-icon"
|
||||
>
|
||||
<icon :type="icon"/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Icon from './aplayer-icon.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: ['icon'],
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less">
|
||||
.aplayer-icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
opacity: .8;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
display: inline;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.aplayer-fill {
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<transition name="slide-v">
|
||||
<div
|
||||
class="aplayer-list"
|
||||
:style="listHeightStyle"
|
||||
ref="list"
|
||||
v-show="show"
|
||||
>
|
||||
<ol
|
||||
ref="ol"
|
||||
:style="listHeightStyle"
|
||||
>
|
||||
<li
|
||||
v-for="(aMusic, index) of musicList"
|
||||
:key="index"
|
||||
:class="{'aplayer-list-light': aMusic === currentMusic}"
|
||||
@click="$emit('selectsong', aMusic)"
|
||||
>
|
||||
<span class="aplayer-list-cur" :style="{background: theme}"></span>
|
||||
<span class="aplayer-list-index">{{ index + 1}}</span>
|
||||
<span class="aplayer-list-title">{{ aMusic.title || 'Untitled' }}</span>
|
||||
<span class="aplayer-list-author">{{ aMusic.artist || aMusic.author || 'Unknown' }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
currentMusic: Object,
|
||||
musicList: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
playIndex: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
theme: String,
|
||||
listmaxheight: String,
|
||||
},
|
||||
computed: {
|
||||
listHeightStyle () {
|
||||
return {
|
||||
height: `${33 * this.musicList.length - 1}px`,
|
||||
maxHeight: this.listmaxheight || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.aplayer-list {
|
||||
overflow: hidden;
|
||||
|
||||
&.slide-v-enter-active,
|
||||
&.slide-v-leave-active {
|
||||
transition: height 500ms ease;
|
||||
will-change: height;
|
||||
}
|
||||
|
||||
&.slide-v-enter,
|
||||
&.slide-v-leave-to {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
li.aplayer-list-light:not(:hover) {
|
||||
background-color: inherit;
|
||||
transition: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:hover) {
|
||||
li.aplayer-list-light {
|
||||
transition: background-color .6s ease;
|
||||
}
|
||||
}
|
||||
li {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 15px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #e9e9e9;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
text-align: start;
|
||||
display: flex;
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
&.aplayer-list-light {
|
||||
background: #efefef;
|
||||
|
||||
.aplayer-list-cur {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.aplayer-list-cur {
|
||||
display: none;
|
||||
width: 3px;
|
||||
height: 22px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 5px;
|
||||
transition: background-color .3s;
|
||||
}
|
||||
.aplayer-list-index {
|
||||
color: #666;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.aplayer-list-title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.aplayer-list-author {
|
||||
flex-shrink: 0;
|
||||
color: #666;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="aplayer-lrc">
|
||||
<div
|
||||
class="aplayer-lrc-contents"
|
||||
:style="transformStyle"
|
||||
>
|
||||
<p
|
||||
v-for="(line, index) of lrcLines"
|
||||
:key="index"
|
||||
:class="{ 'aplayer-lrc-current': index === currentLineIndex }"
|
||||
>
|
||||
{{ line[1] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parseLrc} from '../utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
currentMusic: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
playStat: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
displayLrc: '',
|
||||
currentLineIndex: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lrcLines () {
|
||||
return parseLrc(this.displayLrc)
|
||||
},
|
||||
currentLine () {
|
||||
if (this.currentLineIndex > this.lrcLines.length - 1) {
|
||||
return null
|
||||
}
|
||||
return this.lrcLines[this.currentLineIndex]
|
||||
},
|
||||
transformStyle () {
|
||||
// transform: translateY(0); -webkit-transform: translateY(0);
|
||||
return {
|
||||
transform: `translateY(${-this.currentLineIndex * 16}px)`,
|
||||
webkitTransform: `translateY(${-this.currentLineIndex * 16}px)`,
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
applyLrc (lrc) {
|
||||
if (/^https?:\/\//.test(lrc)) {
|
||||
this.fetchLrc(lrc)
|
||||
} else {
|
||||
this.displayLrc = lrc
|
||||
}
|
||||
},
|
||||
fetchLrc (src) {
|
||||
fetch(src)
|
||||
.then(response => response.text())
|
||||
.then((lrc) => {
|
||||
this.displayLrc = lrc
|
||||
})
|
||||
},
|
||||
hideLrc () {
|
||||
this.displayLrc = ''
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentMusic: {
|
||||
immediate: true,
|
||||
handler (music) {
|
||||
this.currentLineIndex = 0
|
||||
if (music.lrc) {
|
||||
this.applyLrc(music.lrc)
|
||||
} else {
|
||||
this.hideLrc()
|
||||
}
|
||||
}
|
||||
},
|
||||
'playStat.playedTime' (playedTime) {
|
||||
for (let i = 0; i < this.lrcLines.length; i++) {
|
||||
const line = this.lrcLines[i]
|
||||
const nextLine = this.lrcLines[i + 1]
|
||||
if (playedTime >= line[0] && (!nextLine || playedTime < nextLine[0])) {
|
||||
this.currentLineIndex = i
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import "../less/variables";
|
||||
|
||||
.aplayer-lrc {
|
||||
position: relative;
|
||||
height: @lrc-height;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
margin-bottom: 7px;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
content: ' ';
|
||||
background: -moz-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#00ffffff', GradientType=0);
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 33%;
|
||||
content: ' ';
|
||||
background: -moz-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
|
||||
background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffffff', endColorstr='#ccffffff', GradientType=0);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
transition: all 0.5s ease-out;
|
||||
opacity: 0.4;
|
||||
overflow: hidden;
|
||||
|
||||
&.aplayer-lrc-current {
|
||||
opacity: 1;
|
||||
overflow: visible;
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.aplayer-lrc-contents {
|
||||
width: 100%;
|
||||
transition: all 0.5s ease-out;
|
||||
user-select: text;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div
|
||||
class="aplayer-pic"
|
||||
:style="currentPicStyleObj"
|
||||
@mousedown="onDragBegin"
|
||||
@click="onClick"
|
||||
>
|
||||
<div class="aplayer-button" :class="playing ? 'aplayer-pause' : 'aplayer-play'">
|
||||
<icon-button
|
||||
:icon="playing ? 'pause' : 'play'"
|
||||
:class="playing ? 'aplayer-icon-pause' : 'aplayer-icon-play'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import IconButton from './aplayer-iconbutton.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
props: {
|
||||
pic: String,
|
||||
theme: String,
|
||||
playing: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
enableDrag: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hasMovedSinceMouseDown: false,
|
||||
dragStartX: 0,
|
||||
dragStartY: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentPicStyleObj () {
|
||||
if (!this.pic) return {}
|
||||
return {
|
||||
backgroundImage: `url(${this.pic})`,
|
||||
backgroundColor: this.theme
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDragBegin (e) {
|
||||
if (this.enableDrag) {
|
||||
this.hasMovedSinceMouseDown = false
|
||||
this.$emit('dragbegin')
|
||||
this.dragStartX = e.clientX
|
||||
this.dragStartY = e.clientY
|
||||
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||
document.addEventListener('mouseup', this.onDocumentMouseUp)
|
||||
}
|
||||
},
|
||||
onDocumentMouseMove (e) {
|
||||
this.hasMovedSinceMouseDown = true
|
||||
this.$emit('dragging', {offsetLeft: e.clientX - this.dragStartX, offsetTop: e.clientY - this.dragStartY})
|
||||
},
|
||||
onDocumentMouseUp (e) {
|
||||
document.removeEventListener('mouseup', this.onDocumentMouseUp)
|
||||
document.removeEventListener('mousemove', this.onDocumentMouseMove)
|
||||
|
||||
this.$emit('dragend')
|
||||
},
|
||||
onClick () {
|
||||
if (!this.hasMovedSinceMouseDown) {
|
||||
this.$emit('toggleplay')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import "../less/variables";
|
||||
|
||||
.aplayer-float {
|
||||
.aplayer-pic:active {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
.aplayer-pic {
|
||||
flex-shrink: 0;
|
||||
|
||||
position: relative;
|
||||
height: @aplayer-height;
|
||||
width: @aplayer-height;
|
||||
background-image: url(../default.jpg);
|
||||
background-size: cover;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
.aplayer-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.aplayer-button {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
opacity: 0.8;
|
||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
transition: all 0.1s ease;
|
||||
|
||||
.aplayer-fill {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.aplayer-play {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 2px solid #fff;
|
||||
bottom: 50%;
|
||||
right: 50%;
|
||||
margin: 0 -15px -15px 0;
|
||||
.aplayer-icon-play {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 4px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.aplayer-pause {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #fff;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
.aplayer-icon-pause {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user