WY音乐播放器制作(1)

初始化项目

  1. 初始化HTML中的代码

  2. 利用rem+视口释放的方式来适配移动端

注意点: 如果在HTML文件中用到了字符串模板, 字符串模板中用到了变量, 那么html-plugin是无法处理的,所以会报错,如果想解决这个问题,那么我们需要再借助一个loader, html-loader

  1. 借助postcss-pxtorem实现自动将px转换成rem

  2. 借助webpack实现CSS3/ES678语法的兼容

  3. 借助fastclick解决移动端100~300ms的点击事件延迟问题

  4. 初始化默认的全局样式

注意点: 在移动端开发中, 一般情况下我们不需要让字体大小随着屏幕尺寸的变化而变化

​ 由于我们是通过视口缩放来适配移动端的, 所以我们不能直接设置字体大小, 否则字体大小就会随着屏幕尺寸的变化而变化

  1. 注意初始化的文件需要存放导致的位置,和相关的安装和配置(html-loader,postcss-pxtorem,fastclick)
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <!--可以让部分国产浏览器默认采用高速模式渲染页面-->
  <meta name="renderer" content="webkit">
  <!--为了让 IE 浏览器运行最新的渲染模式下-->
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <!--SEO三大标签-->
  <title>WY音乐</title>
  <meta name="keywords"
    content="网易云音乐,音乐,播放器,网易,下载,播放,DJ,免费,明星,精选,歌单,识别音乐,收藏,分享音乐,音乐互动,高音质,320K,音乐社交,官网,移动站,music.163.com">
  <meta name="description" content="网易云音乐是一款专注于发现与分享的音乐产品,依托专业音乐人、DJ、好友推荐及社交功能,为用户打造全新的音乐生活。">
  <!--
    apple-touch-icon: 是苹果私有的属性
    作用: 指定将网页保存到主屏幕上的时候的图标
    -->
  <link rel="apple-touch-icon" href="./apple-touch-icon.png">
  <link rel="apple-touch-icon" sizes="114x114" href="./apple-touch-icon114.png">
  <link rel="apple-touch-icon" sizes="152x152" href="./apple-touch-icon152.png">
  <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon180.png">
  <!--网页快捷图标-->
  <link rel="icon" href="./favicon.ico">
  <script>
    let scale = 1.0 / window.devicePixelRatio;
    let text = `<meta name="viewport" content="width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no">`;
    document.write(text);
    document.documentElement.style.fontSize = window.innerWidth / 7.5 + "px";
    document.documentElement.setAttribute('data-dpr', Math.round(window.devicePixelRatio) + '');
    document.documentElement.setAttribute('data-theme', 'theme');
  </script>
</head>

<body>
  <div id="app"></div>
</body>

</html>

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import fastclick from 'fastclick'
import './assets/css/base.scss'

// 解决移动端100~300ms延迟的问题
fastclick.attach(document.body)
Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

首页制作

效果图

首页

首页2

1. 整体换肤效果的实现(localStorage)

  1. 定义scss的mixin函数,根据头部的data-theme主题,设置背景颜色、文字颜色
  2. 主题存入localStorage
@mixin bg_color(){
  background: $background-color-theme;
  // 属性选择器
  [data-theme=theme1] & {
    background: $background-color-theme1;
  }
  [data-theme=theme2] & {
    background: $background-color-theme2;
  }
}
  
  // 存入localStorage
  localStorage.setItem("theme", this.themes[this.index]);
  
  mounted() {
    // 获取设置主题
    document.documentElement.setAttribute(
      "data-theme",
      localStorage.getItem("theme")
    );
  }

2. 图标适应屏幕

  1. 根据html标签的data-dpr属性来设置大图/中图/小图/字体适应大小不变
主题2
/*根据dpr计算font-size*/
@mixin font_dpr($font-size){
  font-size: $font-size;
  [data-dpr="2"] & { font-size: $font-size * 2;}
  [data-dpr="3"] & { font-size: $font-size * 3;}
}

3.顶栏的封装

顶栏

4.导航栏的路由切换(vue-router)+ 路由懒处理

导航
<template>
  <div class="tabbar">
    <router-link tag="div" class="item" to="/recommend">
      <span>推荐</span>
    </router-link>
    <router-link tag="div" class="item" to="/singer">
      <span>歌手</span>
    </router-link>
    <router-link tag="div" class="item" to="/rank">
      <span>排行</span>
    </router-link>
    <router-link tag="div" class="item" to="/search">
      <span>搜索</span>
    </router-link>
  </div>
</template>
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

// 懒加载处理
const Recommend = () => import("../views/recommend/Recommend")
const Singer = () => import("../views/singer/Singer")
const Rank = () => import("../views/rank/Rank")
const Search = () => import("../views/search/Search")

const routes = [
  {
    path: '',
    // 默认路由
    redirect: '/recommend'
  },
  {
    path: '/recommend',
    component: Recommend
  },
  {
    path: '/singer',
    component: Singer
  },
  {
    path: '/rank',
    component: Rank
  }
  ,{
    path: '/search',
    component: Search
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

5.网络请求的封装(axios)

  1. 封装get / post 请求
  2. 管理请求请求地址
import axios from 'axios'

// 进行一些全局配置
axios.defaults.baseURL = 'http://localhost:3000/'
axios.defaults.timeout = 5000

// 封装自己的get/post方法

export default {
  get: (path = "", data = {}) => {
    return new Promise((resolve, reject) => {
      axios.get(path, {
        params: data
      }).then(response => {
        resolve(response.data);
      }).catch(error => {
        reject(error);
      })
    })
  },
  post: (path = "", data = {}) => {
    return new Promise((resolve, reject) => {
      axios.get(path, data).then(response => {
        resolve(response.data);
      }).catch(error => {
        reject(error);
      })
    })
  }
}
// 专门用于管理请求请求地址
import Network from './network'

// 轮播图
export const getBanner = () => Network.get('banner?type=2')

6.轮播图的展示(vue-awesome-swiper)

轮播图
  1. 此处踩坑swiper的bug, 如果数据时网络获取的,轮播图到最后一张后停止轮播

  2. 解决:swiper标签加上v-if="数据.length > 0"条件解决

  3. 再次踩坑报组件未定义错误

  4. 解决:导入Swiper,SwiperSlide 首字母必须大写,注册组件也需要大写

  5. 再再大意必须安装swiper,vue-awesome-swiper 两个库

<template>
  <!-- swiper的bug, 如果数据时网络获取的,轮播图到最后一张后停止轮播 -->
  <!-- 加上v-if="数据.length > 0"条件解决 -->
  <swiper :options="swiperOption" class="banner" v-if="banners.length > 0">
    <!-- slides -->
    <swiperSlide v-for="value in banners" :key="value.bannerId">
      <img :src="value.pic" />
    </swiperSlide>
    <!-- Optional controls -->
    <div class="swiper-pagination" slot="pagination"></div>
  </swiper>
</template>

<script>
// 这里有坑: Swiper,SwiperSlide 首字母必须大写,注册组件也需要大写
// 否则报组件未定义错误
import { Swiper, SwiperSlide } from "vue-awesome-swiper";
import "swiper/css/swiper.css"; // 必须安装swiper,vue-awesome-swiper

export default {
  name: "Banner",
  props: {
    banners: {
      type: Array,
      required: true
    }
  },
  data() {
    return {
      swiperOption: {
        loop: true, //循环模式
        autoplay: {
          delay: 1500, //自动切换时间 ms
          stopOnLastSlide: false, //当切换到最后一个slide是停止切换
          disableOnInteraction: false // 用户操作swiper之后,是否禁止autoplay
        },
        // 分页器
        pagination: {
          el: ".swiper-pagination"
        },

        // 异步加载
        observer: true,
        observeParents: true,
        observeSlideChildren: true
      }
    };
  },
  components: {
    Swiper,
    SwiperSlide
  }
};
</script>

<style lang="scss" scoped>
.banner {
  img {
    width: 100%;
    height: 300px;
  }
}
</style>

7.个性化展示(组件复用,过滤器使用)

推荐歌单
最新专辑
  1. 两个组件样式一样,可以封装成一个组件(personalized),传入不同的数据渲染
<template>
  <div class="personalized">
    <div class="personalized-top">
      <h3>{{title}}</h3>
    </div>
    <div class="personalized-list">
      <div class="item" v-for="value in personalized" :key="value.id">
        <div class="image">
          <img :src="value.picUrl" :alt="value.name" />
          <div>
            <i></i>
            <span>{{value.playCount | playCount}}</span>
          </div>
        </div>
        <p>{{value.name}}</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "Personalized",
  props: {
    personalized: {
      type: Array,
      required: true
    },
    title: {
      type: String,
      required: true
    }
  },
  filters: {
    // 歌单播放量
    playCount(value) {
      if (value >= 10000) {
        return parseInt(value / 10000) + "万";
        if (value > 100000000) {
          return parseInt(value / 100000000) + "亿";
        }
      } else {
        return value;
      }
    }
  }
};
</script>

8.最新音乐展示

最新音乐
  1. 数据处理(filters)
<template>
  <div class="song">
    <div class="song-top">
      <h3>最新音乐</h3>
    </div>
    <ul class="song-list">
      <li v-for="value in songs" :key="value.id" class="item">
        <img :src="value.song.album.picUrl" />
        <div>
          <h3>{{value | getSongName}}</h3>
          <p>{{value.song.artists | getSingers}}</p>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "SongList",
  props: {
    songs: {
      type: Array,
      required: true
    }
  },
  filters: {
    getSingers(value) {
      let singers = value[0].name;
      let len = value.length;
      while (len > 1) {
        singers += "、" + value[len - 1].name;
        len -= 1;
      }
      return singers;
    },
    getSongName(value) {
      let name = "";
      value.song.alias.length > 0 ? (name = value.name + value.song.alias[0]) : (name = value.name);
      return name
    }
  }
};
</script>

9.图片懒加载处理(vue-lazyload)

  1. 解释:进入主页,系统默认会加载所有的图片,而用户只能看到一屏的图片,造成加载缓慢,用户体验不好
  2. 安装:npm install vue-lazyload --save
  3. 在main.js注册、使用
// 导入 vue-lazyload
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  // 设置占位图片
  loading: require('./assets/images/placeholder.png')
})
  1. 将需要懒加载图片的 :src 修改为 v-lazy

10.滚动效果(IScroll)

  1. 利用IScroll插件来滚动

  2. 遇坑:拖拽没效果

    问题:区域高度和内容高度等高,没有滑动效果

    解决:设置区域内容与需要滚动的视口区域等高

  3. 再遇坑:拖拽卡顿

    问题:元素原生的滚动事件有冲突

    解决:html,body标签选择器 加上 overflow: hidden; touch-action: none;

11.拖拽组件的封装(IScroll、slot 、MutationObserver)

  1. 利用具名插槽来封装滚动组件

  2. 踩坑:不能滚动

    原因:由于数据是网络加载过来的,在IScroll计算高度是,高度不正确

    处理1:利用计时器延时几秒,再计算高度。缺点:加载时间不能确定,用户需要等待,体验差,不推荐

    处理2:利用MutationObserver构造函数,监听元素的改变,重新计算高度

  3. 踩坑:滚动卡顿

    解决:禁用其他事件

    scrollX: false,

    scrollY: true,

    disablePointer: true,

    disableTouch: false,

    disableMouse: true

<template>
  <div id="wrapper" ref="wrapper">
    <div>
      <slot></slot>
    </div>
  </div>
</template>

<script>
// 导入iscroll
import IScroll from "iscroll/build/iscroll-probe";

export default {
  name: "ScrollView",
  mounted() {
    // 这里能拿到页面元素
    this.iscroll = new IScroll(this.$refs.wrapper, {
      mouseWheel: true,
      scrollbars: true,
      // 解决拖拽卡顿问题(禁用其他事件)
      scrollX: false,
      scrollY: true,
      disablePointer: true,
      disableTouch: false,
      disableMouse: true
    });
    // 由于数据时网络获取,这里需要重新设置高度(不推荐使用计时器)
    // setTimeout(() => {
    //   this.iscroll.refresh();
    // }, 3000);

    // 1.创建一个观察者对象
    /**
     * MutationObserver 构造函数只要监听到了指定内容发生类变化,就会执行传入的回调函数
     * mutationList: 发生变化的数组
     * observer: 观察者对象
     */
    let observer = new MutationObserver((mutationList, observer) => {
      this.iscroll.refresh();
    });

    // 2.告诉观察者对象,需要观察什么内容
    let config = {
      childList: true, // 观察目标子节点的变化,添加或删除
      subtree: true, // 默认为false,设置为true可以观察后代子节点
      attributeFilter: ["height", "offsetHeight"] // 观察特定属性
    };

    // 3.告诉观察者对象,我们需要观察什么谁,观察什么内容
    /**
     * 第一个参数: 观察谁
     * 第二个参数: 观察什么内容
     */
    observer.observe(this.$refs.wrapper, config);
  }
};
</script>
END
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容