慕课网vue播放器实战:制作播放页面

21 篇文章 0 订阅
9 篇文章 0 订阅

制作过程遇到的难点:

  1. 制作全屏背景图片:播放页面的背景图片使用了高斯模糊,由于到css的一样样式不太了解,制作起来耗费了一些事件。背景图片的高斯模糊使用了filter样式:filter:blur(20px)。在查阅资料的时候,发现了filter还有一些强大的功能,就是我们国家公祭日的时候,有些网站就会变成了灰色,就是用了filter这个样式:filter: grayscale(100%);。有兴趣的可以去了解一下。
  2. 制作中间区域:因为中间区域是需要了装两个div的,一个是歌曲的图片,另一个是歌词。刚开始的时候准备用定位来实现的,发现不行,苦于没法子,就看了源码,才知道是通过inline-blockno-wrap来实现的。
  3. 制作顶部和底部过渡效果:使用transition标签来制作过渡效果的时候,发现加了样式却没有过渡效果。研究了一下才发现,你不仅要对目标元素添加trasition样式,还要为v-enter-activev-leave-active添加transition样式,否则没有过渡效果。

收获的成果:

1.区分过渡和动画:过渡效果需要特定事件的触发,比如样式的切换等等。而动画是可以自由触发。动画效果可以设置每一步的状态,而过渡效果只能设置起点和终点。当使用原点作为起点的时候,应该用钩子函数enter来实现过渡效果,过渡完会触发trasitionend事件,用dom.addEventListener('transitionend',()=>{})。如果用起点作为终点,在v-enter中设置起点。
2.css中的width和height:一般比较大的就使用百分比就表示,小的就用px来表示。

效果如下:
在这里插入图片描述
自己写的页面样式代码:

<template>
  <div class="player" v-show="playList.length !== 0">
    <transition
      name="normal"
      appear
      @enter="enter"
      @after-enter="afterEnter"
      @leave="leave"
      @after-leave="afterLeave"
    >
      <div class="normal-player" v-show="fullScreen">
        <div class="background">
          <img
            src="https://y.gtimg.cn/music/photo_new/T002R300x300M000003y8dsH2wBHlo.jpg?max_age=2592000"
            alt
          />
        </div>
        <div class="header">
          <div class="close" @click="hide">
            <i class="iconfont icon-fanhui"></i>
          </div>
          <h1 class="title">演员</h1>
          <h2 class="singer">薛之谦</h2>
        </div>
        <div class="middle">
          <div class="middle-l">
            <div class="cd-wrapper" ref="cdWrapper">
              <img
                src="https://y.gtimg.cn/music/photo_new/T002R300x300M000003y8dsH2wBHlo.jpg?max_age=2592000"
                alt
              />
            </div>
            <div class="playing-lyric-wrapper">
              <div class="playing-lyric">演员 (Performer) - 薛之谦 (Joker)</div>
            </div>
          </div>
          <div class="middle-r"></div>
        </div>
        <div class="bottom">
          <div class="dot-wrapper">
            <div class="dot dot-active"></div>
            <div class="dot"></div>
          </div>
          <div class="progress-wrapper">
            <div class="start">0:00</div>
            <div class="progress">
              <div class="progress-line"></div>
              <div class="progress-active"></div>
              <div class="progress-btn"></div>
            </div>
            <div class="end">4:21</div>
          </div>
          <div class="operators">
            <div class="mode">
              <i class="iconfont icon-xunhuan"></i>
            </div>
            <div class="prev">
              <i class="iconfont icon-shangyishou_huaban"></i>
            </div>
            <div class="play-state">
              <i class="iconfont icon-bofang"></i>
            </div>
            <div class="next">
              <i class="iconfont icon-xiayishou_huaban"></i>
            </div>
            <div class="collect">
              <i class="iconfont icon-xihuan"></i>
            </div>
          </div>
        </div>
      </div>
    </transition>
    <div class="mini-player" v-show="!fullScreen" @click="show">
      <div class="icon">
        <img
          src="https://y.gtimg.cn/music/photo_new/T002R300x300M000003y8dsH2wBHlo.jpg?max_age=2592000"
          alt
          height="40"
          width="40"
        />
      </div>
      <div class="text">
        <h1 class="song-name">演员</h1>
        <p class="singer-name">薛之谦</p>
      </div>
      <div class="control">
        <i class="iconfont icon-bofang"></i>
      </div>
      <div class="control">
        <i class="iconfont icon-caidan"></i>
      </div>
    </div>
    <audio src></audio>
  </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'
import animations from 'create-keyframe-animation'

export default {
  data () {
    return {

    }
  },
  computed: {
    ...mapGetters([
      'playList',
      'fullScreen'
    ])
  },
  components: {

  },
  methods: {
    hide () {
      this.setFullScreen(false)
    },
    show () {
      this.setFullScreen(true)
    },
    enter (el, done) {
      const { x, y, scale } = this._getPosAndScale()
      const animation = {
        0: {
          transform: `translate3d(${x}px,${y}px,0) scale(${scale})`
        },
        60: {
          transform: 'translate3d(0,0,0) scale(1.1)'
        },
        100: {
          transform: 'translate3d(0,0,0) scale(1)'
        }
      }
      animations.registerAnimation({
        name: 'move',
        animation,
        presets: {
          duration: 400,
          easing: 'linear'
        }
      })
      animations.runAnimation(this.$refs.cdWrapper, 'move', done)
    },
    afterEnter () {
      animations.unregisterAnimation('move')
      this.$refs.cdWrapper.style.animation = ''
    },
    leave (el, done) {
      const { x, y, scale } = this._getPosAndScale()
      this.$refs.cdWrapper.style.transition = 'all .4s'
      this.$refs.cdWrapper.style.transform = `translate3d(${x}px,${y}px,0) scale(${scale})`
      this.$refs.cdWrapper.addEventListener('transitionend', done)
    },
    afterLeave () {
      this.$refs.cdWrapper.style.transition = ''
      this.$refs.cdWrapper.style.transform = ''
    },
    _getPosAndScale () {
      const paddingTop = 80
      const posX = document.body.clientWidth * 0.5
      const posY = (paddingTop + document.body.clientWidth * 0.8) / 2
      const x2 = 40
      const y2 = document.body.clientHeight - 30
      const x = x2 - posX
      const y = posY + y2
      const scale = 40 / (document.body.clientWidth * 0.8)
      return { x, y, scale }
    },
    ...mapMutations({
      setFullScreen: 'SET_FULL_SCREEN'
    })
  }
}
</script>

<style scoped lang="less">
.player {
  width: 0;
  height: 0;
  .normal-player {
    z-index: 999;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #222222;
    .background {
      position: absolute;
      z-index: -1;
      width: 100%;
      height: 100%;
      opacity: 0.6;
      -webkit-filter: blur(20px);
      filter: blur(20px);
      img {
        height: 100%;
        width: 100%;
      }
    }
    .header {
      position: relative;
      width: 100%;
      height: 60px;
      margin-bottom: 25px;
      .close {
        position: absolute;
        top: 0;
        left: 6px;
        padding: 5px 10px;
        transform: rotate(-90deg);
        i {
          color: #ffcd32;
          font-size: 26px;
        }
      }
      .title {
        height: 40px;
        text-align: center;
        font-size: 18px;
        color: white;
        font-weight: 500;
        line-height: 40px;
      }
      .singer {
        height: 20px;
        text-align: center;
        font-size: 15px;
        color: white;
        line-height: 20px;
        font-weight: 500;
      }
    }
    .middle {
      position: fixed;
      width: 100%;
      top: 80px;
      bottom: 170px;
      white-space: nowrap;
      .middle-l {
        display: inline-block;
        position: relative;
        padding-top: 80%;
        width: 100%;
        height: 0;
        vertical-align: top;
        .cd-wrapper {
          position: absolute;
          top: 0;
          left: 10%;
          width: 80%;
          height: 100%;
          img {
            width: 100%;
            height: 100%;
            box-sizing: border-box;
            border-radius: 50%;
            border: 10px solid #979797;
          }
        }
        .playing-lyric-wrapper {
          margin: 30px 36px 0 36px;
          text-align: center;
          font-size: 14px;
          color: hsla(0, 0%, 100%, 0.5);
        }
      }
      .middle-r {
        display: inline-block;
        position: relative;
        padding-top: 80%;
        width: 100%;
        height: 0;
      }
    }
    .bottom {
      position: absolute;
      bottom: 50px;
      width: 100%;
      .dot-wrapper {
        display: flex;
        justify-content: center;
        .dot {
          width: 8px;
          height: 8px;
          margin-right: 8px;
          border-radius: 50%;
          background-color: #aaa9a9;
        }
        .dot-active {
          width: 20px;
          background-color: #dddddd;
          border-radius: 8px;
        }
      }
      .progress-wrapper {
        display: flex;
        justify-content: center;
        height: 30px;
        margin: 0 36px;
        padding: 10px 0;
        color: #dddddd;
        font-size: 12px;
        line-height: 30px;
        .progress {
          position: relative;
          flex: 1;
          margin: 0 10px;
          .progress-line {
            position: absolute;
            top: 50%;
            width: 100%;
            height: 4px;
            transform: translateY(-50%);
            background-color: #656565;
          }
          .progress-active {
            position: absolute;
            top: 50%;
            width: 1%;
            height: 4px;
            transform: translateY(-50%);
            background-color: #ffcd32;
          }
          .progress-btn {
            position: absolute;
            top: 50%;
            left: 0;
            width: 16px;
            height: 16px;
            transform: translateY(-50%);
            border: 3px solid white;
            border-radius: 50%;
            box-sizing: border-box;
            background-color: #ffcd32;
          }
        }
      }
      .operators {
        display: flex;
        margin: 0 36px;
        i {
          font-size: 34px;
          color: #ffcd32;
        }
        div {
          flex: 1;
          line-height: 46px;
          text-align: center;
        }
        .play-state {
          padding: 0 10px;
          i {
            font-size: 46px;
          }
        }
        .mode {
          text-align: left;
        }
        .collect {
          text-align: right;
          i {
            color: white;
          }
        }
        .mode {
          i {
            font-size: 20px;
          }
        }
      }
    }
  }
  .mini-player {
    z-index: 999;
    display: flex;
    position: fixed;
    bottom: 0;
    right: 0;
    left: 0;
    height: 40px;
    background-color: #333333;
    padding: 10px 0;
    .icon {
      padding: 0 10px 0 20px;
      img {
        border-radius: 50%;
      }
    }
    .text {
      flex: 1;
      .song-name {
        font-weight: 500;
        color: white;
        font-size: 15px;
      }
      .singer-name {
        font-size: 13px;
        color: #8a8a8a;
      }
    }
    .control {
      padding-right: 10px;
      margin-left: 10px;
      i {
        font-size: 35px;
        color: #c5a231;
      }
    }
  }
}
.normal-enter,
.normal-leave-to {
  opacity: 0;
  .header {
    transform: translate3d(0, -100%, 0);
  }
  .bottom {
    transform: translate3d(0, 100%, 0);
  }
}
.normal-enter-active,
.normal-leave-active {
  transition: all 0.4s;
  .header {
    transition: all 0.4s cubic-bezier(0.86, 0.18, 0.82, 1.32);
  }
  .bottom {
    transition: all 0.4s cubic-bezier(0.86, 0.18, 0.82, 1.32);
  }
}
</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值