如下只是學習完慕課網huangyi老師實戰視頻課程的筆記內容,僅供我的參考學習使用。
若是對Vue2.0實戰高級-開發移動端音樂WebApp
感興趣的話,請移步這裏:
https://coding.imooc.com/clas...
謝謝。html
項目GitHub地址: https://github.com/bjw1234/vu...vue
項目演示地址: http://music.baijiawei.topwebpack
// 安裝vue腳手架工具 npm install vue-cli -g // 初始化webpack應用 vue init webpack vue-music
// 背景圖片 bg-image($url) background-image: url($url + "@2x.png") @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3) background-image: url($url + "@3x.png") // 不換行 no-wrap() text-overflow: ellipsis overflow: hidden white-space: nowrap // 擴展點擊區域 extend-click() position: relative &:before content: '' position: absolute top: -10px left: -10px right: -10px bottom: -10px
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), 'common': resolve('src/common') } }
fastclick:
處理移動端click事件300毫秒延遲和點透問題。git
先執行安裝fastclick的命令。github
npm install fastclick --save
以後,在main.js中引入,並綁定到bodyweb
import FastClick from 'fastclick'; FastClick.attach(document.body);
注意: 當fastclick
和其餘的模塊點擊衝突,致使點擊事件不可用時,能夠給對應的dom添加needsclick
類來解決。vuex
下載原始的jsonp
模塊:vue-cli
npm install jsonp --save
再次封裝:npm
import originJSONP from 'jsonp'; /** * 作一個簡單的jsonp封裝 * @param url * @param data * @param option * @return {Promise} */ export default function jsonp (url, data, option) { return new Promise((resolve, reject) => { url = `${url}?${_obj2String(data)}`; originJSONP(url, option, (err, data) => { if (!err) { resolve(data); } else { reject(err); } }); }); }; function _obj2String (obj, arr = [], index = 0) { for (let item in obj) { arr[index++] = [item, obj[item]]; } return new URLSearchParams(arr).toString(); }
注意: 當使用keep-alive
組件時,當切換到其餘路由,會調用前組件的deactivated
鉤子函數,當切回來時,會調用activated
函數。json
注意:
初始化:
import BScroll from 'better-scroll'; let wrapper = document.querySelector('.wrapper'); let scroll = new BScroll(wrapper,{ // 配置項 });
.wrapper position: fixed width: 100% top: 88px bottom: 0 .scroll height: 100% overflow: hidden
問題排查(沒法滾動緣由:)
better-scroll
。refresh
方法。當在開發模式下,須要使用一些後臺接口,爲了防止跨域問題,vue-cli
提供了很是強大的http-proxy-middleware
包。能夠對咱們的請求進行代理。
進入 config/index.js
代碼下以下配置便可:
proxyTable: { '/getDescList': { target: 'http://127.0.0.1:7070/desclist', // 後端接口地址 changeOrigin: true, // secure: false, pathRewrite: { '^/getDescList': '/' } } }
marin-left
或者margin-top
是負值:它會將元素在相應的方向進行移動。left
就是左右方向移動,top
就是上下方向移動。也就是會使元素在文檔流裏的位置發生變化
。margin-right
或者margin-bottom
是負值:它不會移動該元素(該元素不變化),但會使該元素後面的元素往前移動。也就是說,若是margin-bottom
爲負值,那麼該元素下面的元素會往上
移動;若是margin-right
爲負值,那麼該元素右邊的元素會往左
移動,從而覆蓋該元素。需求:在歌手頁面下須要一個歌手詳情頁。
export default new Router({ routes:[ { path: '/', component: Singer, children: [ { path: ':id', compoonent: SingerDetail } ] }, ... ] });
當監聽到用戶點擊以後進行路由跳轉:
this.$router.push({ path: `singer/${singer.id}` }); // 別忘了在`Singer`頁面中: <router-view></router-view>
簡單來講:Vuex
解決項目中多個組件之間的數據通訊和狀態管理。
Vuex將狀態管理單獨拎出來,應用統一的方式進行處理,採用單向數據流的方式來管理數據。用處負責觸發動做(Action
)進而改變對應狀態(State
),從而反映到視圖(View
)上。
安裝:
npm install vuex --save
引入:
import Vuex from 'vuex'; import Vue from 'Vue'; Vue.use(Vuex);
Vuex的組成部分
使用Vuex開發的應用結構應該是這樣的:
State
負責存儲整個應用的狀態數據,通常須要在使用的時候在根節點注入store
對象,後期就可使用this.$store.state
直接獲取狀態。
import store from './store'; .. new Vue({ el: '#app', store, render: h => h(App) });
那麼這個store
又是什麼?從哪來的呢?
store
能夠理解爲一個容器,包含應用中的state
。實例化生成store
的過程是:
const mutations = {...}; const actions = {...}; const state = {...}; // 實例化store對象並導出 export defautl new Vuex.Store({ state, actions, mutations });
中文意思是「變化」,利用它能夠來更改狀態,本質上就是用來處理數據的函數。store.commit(mutationName)
是用來觸發一個mutation
的方法。
須要記住的是,定義的mutation必須是同步函數。
const mutations = { changState(state) { // 在這裏改變state中的數據 } }; // 能夠在組件中這樣觸發 this.$store.commit('changeState');
Actions也能夠用於改變狀態,不過是經過觸發mutation
實現的,重要的是能夠包含異步操做。
直接觸發可使用this.$store.dispatch(actionName)
方法。
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); // 狀態 const state = { singer: {} }; // 跟蹤狀態的變化 const mutations = { setSinger (state, singer) { state.singer = singer; } }; // 實例化store對象 export default new Vuex.Store({ state, mutations }); // 在singer組件中提交數據 this.$store.commit('setSinger',singer); // 在singer-detail組件中接收數據 let singer = this.$store.state.singer;
在上面的小栗子中,咱們把sate
、mutations
等其餘一些內容寫在了一塊兒,
可是這種方式不適合大型點的項目。最好能將這些內容拎出來,單獨做爲一個文件來使用。
在src/store目錄中新建如下文件:
const sate = { singer: {} }; export default state;
export const SET_SINGER = 'SET_SINGER';
import * as types from './mutation-types'; // 經過這個函數能夠傳入payload信息 const mutations = { [types.SET_SINGER](state,singer){ state.singer = singer; } }; export default mutations;
export const singer = state => state.singer;
// 暫時沒有什麼異步操做
// 入口文件 import Vue from 'vue'; import Vuex from 'vuex'; import state from './state'; import mutations from './mutations'; import * as actions from './actions'; import * as getters from './getters'; import createLogger from 'vuex/dist/logger'; Vue.use(Vuex); // 調試環境下開啓嚴格模式 const debug = process.env.NODE_ENV !== 'production'; // 建立store對象並導出 export default new Vuex.Store({ state, actions, getters, mutations, strict: debug, plugins: debug ? [createLogger()] : [] });
使用:
// main.js中引入 import store from './store';
有了以上內容,那麼咱們就能夠在業務中去使用了:
例如:多組件之間的的數據交互。
需求:singer
組件中須要將用戶點擊的那個singer對象
傳遞給組件singer-detail
組件。
singer.vue 組件中:
// 使用這個語法糖 import { mapMutations } from 'vuex'; methods:{ ...mapMutations({ // 將這個函數(setSinger)和mutations中用於修改狀態的函數關聯起來 setSinger: 'SET_SINGER' }); } // 傳參 this.setSinger(singer); // 語法糖的本質 this.$store.commit('setSinger', singer);
singer-detail.vue 組件中:
咱們就能夠去使用這個數據了,固然也是使用咱們的語法糖啦。
import { mapGetters } from 'vuex'; export default { // 使用一個計算屬性 computed: { ...mapGetters([ 'singer' // 這個就是getters.js中的那個singer ]); }, created(){ console.log(this.singer); } } // 語法糖的本質: let singer = this.$store.state.singer;
咱們必定遇到過這種狀況:
須要用JS寫CSS動畫。但咱們又不得不處理前綴的問題。
因此通常是這樣寫的:
this.$refs.image.style.transform = `scale(${scale})`; this.$refs.image.style.webkitTansform = `scale(${scale})`; ...
那麼問題來了,怎樣用JS處理這種狀況呢?
思路:
代碼實現:
let elementStyle = document.createElement('div').style; // 獲得合適的瀏覽器前綴 let vendor = (() => { let transformNames = { webkit: 'webkitTransform', Moz: 'MozTransform', O: 'OTransform', ms: 'msTransform', standard: 'transform' }; for (let key in transformNames) { let support = elementStyle[transformNames[key]] !== undefined; if (support) { return key; } } return false; })(); // 對外暴露的方法 export function prefixStyle (style) { if (vendor === false) { return style; } if (vendor === 'standard') { return style; } let result = vendor + style.charAt(0).toUpperCase() + style.substr(1); return result; }
使用案例:
// 導入該模塊 import { prefixStyle } from 'common/js/dom'; // 加了合適前綴的CSS屬性 const TRANSFORM = prefixStyle('transform'); // 使用該CSS屬性 this.$refs.image.style[TRANSFORM] = `scale(${scale})`;
隨着觸屏設備的普及,w3c爲移動端web新增了touch事件。
最基本的touch事件包括4個事件:
當用戶手指觸摸到的觸摸屏的時候觸發。事件對象的 target
就是 touch
發生位置的那個元素。
即便手指移出了 原來的target
元素,但 touchmove
仍然會被一直觸發,並且 target
仍然是原來的 target
元素。
當用戶的手指擡起的時候,會觸發 touchend
事件。若是用戶的手指從觸屏設備的邊緣移出了觸屏設備,也會觸發 touchend
事件。
touchend
事件的 target
也是與 touchstart
的 target
一致,即便已經移出了元素。
若是你使用了觸摸事件,能夠調用 event.preventDefault()來阻止鼠標事件被觸發。
與移動端相關的interface
主要有三個:
能夠經過檢查觸摸事件的 TouchEvent.type
屬性來肯定當前事件屬於哪一種類型。
dom.addEventListener('touchstart',(e) => { // 獲取事件類型 let type = e.type; // toch事件發生時那個位置的元素對象 let target = e.target; });
screenX
、screenY
:觸點相對於屏幕左邊緣或上邊緣的x、y座標。clientX
、clientY
:觸點相對於瀏覽器viewport左邊緣或上邊緣的x、y座標。(不包含滾動距離)
pageX
、pageY
:觸點相對於document的左邊緣或上邊緣的x、y座標。與client不一樣的是,包含左邊滾動的距離。
target
:觸摸開始時的element。
// 獲取touchList let touchList = e.changedTouches; // 獲取第i個touch對象 let touch = touchList[i]; touch.screenX touch.clientX touch.pageX touch.target ...
若是一個用戶用三根手指接觸屏幕(或者觸控板), 與之相關的TouchList
對於每根手指都會生成一個 Touch
對象, 共計 3 個.
能夠經過三種方式獲取這個對象:
dom.addEventListener('touchstart',(e) => { // 這個 TouchList對象列出了和這個觸摸事件對應的那些發生了變化的 Touch 對象 e.changedTouches // 這個TouchList列出了那些 touchstart發生在這個元素,而且尚未離開 touch surface 的touch point(手指) e.targetTouches // 這個 TouchList 列出了事件觸發時: touch suface上全部的 touch point。 e.touches });
對於音樂的播放,咱們使用了audio
標籤,監聽它的事件和操做DOM,能夠達到對音樂播放、
暫停、進度控制等操做。
<audio ref="audio" :src="currentSongUrl" @canplay="songCanPlay" @error="songError" @ended="songEnd" @timeupdate="updateTime"> </audio>
對audio
進行操做
let audio = this.$refs.audio; // 暫停和播放 audio.pause(); audio.play(); // Audio對象的屬性(部分) audio.currentTime // 設置或返回音頻中的當前播放位置(以秒計)。 audio.duration // 返回音頻的長度(以秒計)。 audio.loop // 設置或返回音頻是否應在結束時再次播放。(默認false) audio.volume // 設置或返回音頻的音量。[0,1] // Audio對象多媒體事件(Media Events) onerror // 加載發生錯誤時的回調 ontimeupdate // 當播放位置改變時調用 updateTime(e) { if(this.currentSongReady){ // 獲取當前播放的進度 this.currentSongTime=e.traget.currentTime; } } oncanplay // 可以播放時調用 // 經過監聽這個事件,設置標誌位,這個標誌位能夠幫助咱們 // 防止用戶快速切換歌曲引發一些錯誤。 songCanPlay(){ this.currentSongReady = true; } onended // 到達結尾時調用 onplay、onpause...
1.progress-bar.vue
接收一個percent
參數,用來顯示當前播放的一個進度。
2.對於進度條用戶手動拖動進度的實現。
<div class="progress-btn" ref="btn" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"> </div>
思路:主要是經過監聽ontouchstart
、ontouchmove
、ontouchend
事件來完成。
// 首先得定義一個`touch`對象 let touch = {}; // 在監聽的方法中 touchStart(e){ this.touch.initialized = true; // 獲取touch的起始位置 this.touch.startX = e.touches[0].pageX; // 獲取整個進度條的寬度 this.touch.barW = xxx; // 獲取已經播放的進度 this.touch.offset = xxx; } touchMove(e){ // 判斷有無初始化 ... // 獲取用戶滑動的距離 let deltaX = e.touches[0].pageX - this.touch.startX; let barW = xxx; // 進度條的寬度 - 拖動btn的寬度 let offset = Math.min(Math.max(0, this.touch.offset + detail), barW); // 最後設置btn的位置和progress的進度就OK ... } touchEnd(){ this.touch.initialized = false; // 而後將進度推送出去就行了 this.$emit('percentChange',percent); }
<template> <div class="progress-circle"> <svg :width="radius" :height="radius" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg"> <circle class="progress-background" r="50" cx="50" cy="50" fill="transparent"/> <circle class="progress-bar" r="50" cx="50" cy="50" fill="transparent" :stroke-dasharray="dashArray" :stroke-dashoffset="offset"/> </svg> <slot></slot> </div> </template>
經過svg
能夠實現各類進度條,有一個問題,怎樣去動態的修改它的進度值呢?
這就不能不提 SVG Stroke 屬性
OK,知道了以上屬性,就足以實現一個可設置進度的SVG進度條了。
思路:stroke-dasharray
適用於建立虛線的,若是這個虛線長度爲整個輪廓的周長呢。stroke-dashoffset
能夠設置虛線的偏移量,利用這兩個屬性,咱們就能夠完成對進度的控制。
且看一個小栗子:
因此,經過父組件傳入的percent
,不斷地修改stroke-dashoffset
就能達到進度的顯示了。
// 全屏顯示 document.documentElement.webkitRequestFullScreen(); // 退出全屏 document.webkitExitFullscreen(); // 1.得根據不一樣的瀏覽器添加前綴 // 2.程序主動調用無論用,得用戶操做才能夠(點擊按鈕)
經過網絡接口獲取的歌詞:
對於歌詞的解析,播放是經過一個插件lyric-parser完成的。
這個插件很簡單:
1.經過正則把時間和對應的歌詞切分出來建立成對象。
2.當調用play
方法時,經過定時器完成歌詞的播放,並將對應的行號和歌詞經過回調函數傳遞出去。
當播放的歌詞超過5行時,就可使用封裝的scroll
組件完成滾動操做。
if (lineNum > 5) { let elements = this.$refs.lyricLine; this.$refs.lyricScroll.scrollToElement(elements[lineNum - 5], 1000); } else { this.$refs.lyricScroll.scrollTo(0, 0, 1000); }
爲何要使用mixin?
多個組件公用同樣的代碼,咱們能夠將這部分抽離出來做爲mixin
,只要引入對應的組件中就能夠了。
例以下面的mixin
:
import { mapGetters } from 'vuex'; export const playListMixin = { mounted () { this.handlePlayList(this.playList); }, // 當路由對應的頁面激活時調用 activated () { this.handlePlayList(this.playList); }, watch: { playList (newPlayList) { this.handlePlayList(newPlayList); } }, computed: { ...mapGetters([ 'playList' ]) }, methods: { // 這個方法須要對應的組件本身去實現,直接調用拋出錯誤 handlePlayList () { throw new Error('Components must implement handlePlayList method.'); } } };
有了mixin
咱們在組件中就能夠這樣使用了:
import { playListMixin } from 'common/js/mixin'; export default{ mixins: [playListMixin], ... }
在搜索頁面,咱們須要處理用戶的輸入,而後向服務器發起請求。
爲了避免必要的請求、節省流量和提升頁面性能,咱們都有必要作節流處理。
在搜索框search-box
這個基礎組件中:
// 在created鉤子中,咱們監聽用戶輸入字符串(query)變化,而後將變化後的字符串 // 提交給父組件 // 能夠看到在回調函數中,又包了一層debounce函數 created () { this.$watch('query', debounce(() => { this.$emit('queryChange', this.query); }, 500)); }
因此debounce
函數,就是咱們的節流函數,這個函數,接收一個函數,返回一個新的函數
function debounce(func,delay){ let timer = null; return function(...args){ if(timer){ clearTimeout(timer); } timer = setTimeout(()=>{ func.apply(this,args); },delay) } } // 測試 function show(){ console.log('hello...'); } var func = debounce(show,3000); // 調用 func(); // 連續調用時,沒有超過三秒是不會有任何輸出的
語法:
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
animation: 動畫名稱 執行時間 速度曲線 延時時間 執行次數 動畫播放順序 結束時應用的樣式 播放的狀態(paused|running)
const __VERSION__ = '1.0.1'; const store = { version: __VERSION__, storage: window.localStorage, session: { storage: window.sessionStorage } }; // 操做store的api const api = { set (key, val) { if (this.disabled) { return false; } if (val === undefined) { return this.remove(key); } this.storage.setItem(key, this.serialize(val)); return val; }, get (key, val) { if (this.disabled) { return false; } let result = this.storage.getItem(key); if (!result) { return val; } return this.deSerialize(result); }, getAll () { if (this.disabled) { return false; } let ret = {}; for (let key in this.storage) { if (this.storage.hasOwnProperty(key)) { ret[key] = this.get(key); } } return ret; }, remove (key) { if (this.disabled) { return false; } this.storage.removeItem(key); }, removeAll () { if (this.disabled) { return false; } this.storage.clear(); }, forEach (cb) { if (this.disabled) { return false; } for (let key in this.storage) { if (this.storage.hasOwnProperty(key)) { cb && cb(key, this.get(key)); } } }, has (key) { if (this.disabled) { return false; } return key === this.get(key); }, serialize (val) { try { return JSON.stringify(val) || undefined; } catch (e) { return undefined; } }, deSerialize (val) { if (typeof val !== 'string') { return undefined; } try { return JSON.parse(val) || undefined; } catch (e) { return undefined; } } }; // 擴展store對象 Object.assign(store, api); Object.assign(store.session, api); // 瀏覽器能力檢測 try { let testKey = 'test_key'; store.set(testKey, testKey); if (store.get(testKey) !== testKey) { store.disabled = true; } store.remove(testKey); } catch (e) { store.disabled = true; } export default store;
爲何須要?
若是開發的App太大的話,就會致使首屏渲染過慢,爲了加強用戶體驗,加快渲染速度,
須要用到懶加載功能。讓首屏的內容先加載出來,其餘路由下的組件按需加載。
vue官網描述:
異步組件
在大型應用中,咱們可能須要將應用分割成小一些的代碼塊,而且只在須要的時候才從服務器加載一個模塊。
爲了簡化,Vue 容許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。
Vue 只有在這個組件須要被渲染的時候纔會觸發該工廠函數,且會把結果緩存起來供將來重渲染。
const AsyncComponent = () => ({ // 須要加載的組件 (應該是一個 `Promise` 對象) component: import('./MyComponent.vue'), // 異步組件加載時使用的組件 loading: LoadingComponent, // 加載失敗時使用的組件 error: ErrorComponent, // 展現加載時組件的延時時間。默認值是 200 (毫秒) delay: 200, // 若是提供了超時時間且組件加載也超時了, // 則使用加載失敗時使用的組件。默認值是:`Infinity` timeout: 3000 })
注意:若是你但願在 Vue Router 的路由組件中使用上述語法的話,你必須使用 Vue Router 2.4.0+ 版本。
固然爲了簡單起見:
在router/index.js
路由配置文件中這樣加載組件:
// import Recommend from '@/components/recommend/recommend'; const Recommend = () => ({ component: import('@/components/recommend/recommend') });