1.前言
vue用了有一段時間了,開發的後臺管理系統也趨於完善,現在時間比較算是有點空閒吧!這個空閒時間我在研究vue的另外的一些玩法,比如元件,外掛等。今天,我就分享一個元件的練手專案–焦點圖切換元件。這個專案是我用於vue元件練習的一個專案,當然了,程式碼也會提交到github(ec-slider),也會維護。我也想我開發的東西好用一點!現在,就是建議有需要的夥伴,可以來玩下這個專案,當練習的作用!另外,如果大家有什麼建議,歡迎指點!
建議
1.下面的步驟,最好在自己本地上跑起來,根據文章的步驟,逐步完成,如果只看程式碼,很容易懵逼的。
2.如果不清楚哪個程式碼有什麼作用,可能自己除錯下,把程式碼去掉後,看下有什麼影響,就很容易想出程式碼有什麼作用了!
2.專案目錄
很普通,很好理解的一個目錄,但還是簡單的解釋一下吧
node_modules
:檔案依賴模組(自動生成)dist
:打包檔案產出目錄(自動生成)src
:開發檔案目錄src/components
:元件檔案目錄.babelrc
:babel編譯es6的配置檔案.gitnore
:不提交到git的檔案(目錄)的配置檔案fontSize
:設定rem演算法的檔案(現在沒用到,忽略)index.html
:模板檔案index.js
:入口檔案package.json
:配置檔案README.md
:說明文件webpack.config.babel.js
:webpack配置檔案
3.步驟詳解
3-1跑起來
這是專案的第一步(專案搭建這個,我不多說,之前的文章已經說了幾次了!),現在src/components/ec-slider.vue
這裡輸出一個‘守候’
1.首先,在src/components/ec-slider.vue
裡面輸出‘守候’,程式碼如下
<template>
<div>
守候
</div>
</template>
<script type="text/javascript">
export default {
data () {
return {
}
},
computed: {
},
mounted(){
},
props: [],
methods: {
}
}
</script>複製程式碼
2.然後,在src/components/index.js
裡面設定註冊元件(要帶一個install方法),程式碼如下
import SlideImg from `./ec-slider.vue`
const ecslide={
install:function (Vue) {
Vue.component(`ec-slide`,SlideImg)
}
}
export default ecslide;複製程式碼
3.在入口檔案,index.js裡面引入並且使用元件
require("./index.html");
require("./src/sass/index.scss");
import Vue from `vue`
//引入並且使用元件
import ecslide from `./src/js/components/index`;
Vue.use(ecslide);
let app6 = new Vue({
el: "#app6",
data: {
},
mounted(){
}
});複製程式碼
4.在index.html(模板檔案),輸出元件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>Title</title>
</head>
<body>
<div id="app6">
<ec-slide></ec-slide>
</div>
</body>
</html> 複製程式碼
5.命令列輸入$ npm run dev
跑起來,結果完美!這幾步的原理貌似沒什麼可多說的,都是固定式的步驟。
3-2開發準備
經過上一步之後,基礎就已經打好了,那麼接下來就是一個開發的過程,大部分都是修改src/components/ec-slider.vue
這個檔案。
開發之前,大家不要急著寫程式碼,先分析下當中的執行流程!
首先,一個焦點圖切換,需要什麼引數?根據下面的一個淘寶栗子,我簡單分析下,就是下面這幾個!
list
-圖片列表[{src:`url`,href:`www.baidu.com`},{src:`url`,href:`www.163.com`}](src:圖片的src,href:跳轉連線,點選圖片的時候)autoplay
-是否自動播放 布林 (預設false)type
-輪播方式‘transparent’(透明度切換), `slide`(滑動切換) (預設slide)option
-對應切換 (預設false,不顯示)time
-輪播間隔時間,毫秒 (預設4000)sildetype
-過渡效果 (預設`ease`慢速開始,然後變快,然後慢速結束的過渡效果,參考:transition-timing-function)arrowurl
-箭頭圖片連結arrowsize
-箭頭尺寸‘width,height’direction
-切換方向`left`(左右) `top`(上下) (預設:左右)
分析完了之後,就知道暫時需要這麼多引數,那麼接下來就是在ec-slider.vue
裡面,接收這些引數。父子元件傳參方式,我想大家知道–props
。程式碼如下
<template>
<div>
守候
</div>
</template>
<script type="text/javascript">
export default {
data () {
return {
}
},
computed: {
},
mounted(){
},
props: [`list`, `autoplay`, `type`, `time`, `sildetype`, `arrowurl`,`arrowsize`,`option`,`direction`],
methods: {
}
}
</script>複製程式碼
有地方接收引數,肯定要有地方傳引數,就是index.html
模板檔案裡面傳
<div class="slider-left">
<ec-slide :list=`list` :autoplay="true" :type="`slide`" :option="true" :time="4000" :sildetype="`ease`" :arrowurl="`http://i1.buimg.com/1949/4d860a3067fab23b.jpg`" :arrowsize="`20,40`" :direction="`left`"></ec-slide>
</div> 複製程式碼
3-3樣式佈局
既然知道了,會接收什麼引數,那下面先把樣式佈局,給弄好先,這個不多說,程式碼如下!(有些解釋我也是直接打到程式碼上)
<template>
<div class="switch-img-box" id="ec-slide-box">
<div class="switch-img-type switch-img-left">
<ul :style="{`width`:ulWidth,`transition-timing-function`:slideChange}">
<li v-for="(li,index) in list" :style="{`width`:listWidth+`%`}">
<a :href="li.href?li.href:`javascript:;`">
<img :src="li.src" class="slider-img"/>
</a>
</li>
</ul>
</div>
<!--如果需要顯示對應的點-->
<div class="switch-option" v-if="option">
<div>
<span v-for="(li,index) in list"></span>
</div>
</div>
<!--如果需要顯示箭頭-->
<div class="switch-arrow" v-if="arrowurl&&arrowsize">
<div :class="{`arrow-left`:direction===`left`,`arrow-top`:direction===`top`}"
:style="{`width`:arrowWidth+`px`,`height`:arrowHeight+`px`,`background`:`url(`+arrowurl+`) no-repeat`,`background-size`:`100%`}"></div>
<div :class="{`arrow-right`:direction===`left`,`arrow-bottom`:direction===`top`}"
:style="{`width`:arrowWidth+`px`,`height`:arrowHeight+`px`,`background`:`url(`+arrowurl+`) no-repeat`,`background-size`:`100%`}"></div>
</div>
</div>
</template>
<script type="text/javascript">
export default {
data () {
return {
slideChange: ``,
arrowWidth: ``,
arrowHeight: ``,
}
},
computed: {
//ul寬度
ulWidth: function () {
return (this.list.length) + "00%";
},
//li寬度
listWidth: function () {
return 100 / (this.list.length)
}
},
mounted(){
//設定各個資料初始值
this.slideChange = this.sildetype || `ease`;
if (this.arrowsize && this.arrowurl) {
this.arrowWidth = this.arrowsize.split(`,`)[0];
this.arrowHeight = this.arrowsize.split(`,`)[1];
}
},
props: [`list`, `autoplay`, `type`, `time`, `sildetype`, `arrowurl`, `arrowsize`, `option`, `direction`],
methods: {
}
}
</script>
<style lang="scss">
.ec-slide-img-box {
width: 100%;
height: 100%;
position: relative;
touch-action: none;
}
.ec-slide-img-type {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
&.ec-slide-img-top {
}
&.ec-slide-img-left {
li {
display: inline-block;
font-size: 0;
}
}
&.ec-slide-img-transparent {
li {
opacity: 0;
transition: opacity 1s;
width: 0;
&.cur {
width: auto;
}
&.show {
opacity: 1;
}
}
}
ul {
font-size: 0;
&.tran {
transition: all .4s;
}
li {
text-align: center;
}
img {
vertical-align: middle;
max-width: 100%;
max-height: 100%;
}
}
}
.ec-slide-arrow {
div {
position: absolute;
z-index: 2;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
opacity: .5;
&:hover {
opacity: 1;
}
&.arrow-left {
left: 10px;
right: auto;
}
&.arrow-right {
right: 10px;
left: auto;
transform: rotate(180deg);
}
&.arrow-top {
top: 10px;
bottom: auto;
}
&.arrow-bottom {
bottom: 10px;
top: auto;
transform: rotate(180deg);
}
}
}
.ec-slide-option {
position: absolute;
font-size: 0;
bottom: 10px;
text-align: center;
width: 100%;
z-index: 5;
&.isFirst {
span:first-child {
display: none;
}
}
&.isLast {
span:last-child {
display: none;
}
}
span {
border-radius: 100%;
margin: 0 5px;
background: #fff;
display: inline-block;
width: 10px;
height: 10px;
&.active {
background: #09f;
}
}
&.ec-slide-option-top {
display: table;
width: 10px;
height: 100%;
top: 0;
right: 10px;
margin: auto;
bottom: 0;
span {
margin: 5px 0;
}
div {
display: table-cell;
vertical-align: middle;
}
}
}
</style>
複製程式碼
執行結果,就是下面這樣
3-4執行動畫
佈局搞定了,下面就可以寫動畫,讓輪播動起來!這裡也需要增加幾個變數,一個是nowIndex,記錄當前索引。一個是timer定時器!
首先,我用transform:translate3d()
這個方式控制ul
的滑動。
<ul :style="{`width`:ulWidth,`transform`:`translate3d(-`+(listWidth*(nowIndex))+`%,0,0)`,`transition-timing-function`:slideChange,`transition`: `all .4s`}">
<li v-for="(li,index) in list" :style="{`width`:listWidth+`%`}">
<a :href="li.href?li.href:`javascript:;`">
<img :src="li.src" class="slider-img"/>
</a>
</li>
</ul>複製程式碼
然後,根據nowIndex,設定對應點的class。
<div class="switch-option" v-if="option">
<div>
<!--如果當前索引index等於nowIndex。則新增active這個class,點就會變成藍色-->
<span v-for="(li,index) in list" :class="{`active`:index===nowIndex}"></span>
</div>
</div>複製程式碼
js程式碼如下
<script type="text/javascript">
export default {
data () {
return {
nowIndex: 0,
timer: null,
slideChange: ``,
arrowWidth: ``,
arrowHeight: ``,
}
},
computed: {
//ul寬度
ulWidth: function () {
return (this.list.length) + "00%";
},
//li寬度
listWidth: function () {
return 100 / (this.list.length)
}
},
mounted(){
//是否自動播放
if (this.autoplay) {
this.autoSwitch();
}
//設定初始值
this.slideChange = this.sildetype || `ease`;
if (this.arrowsize && this.arrowurl) {
this.arrowWidth = this.arrowsize.split(`,`)[0];
this.arrowHeight = this.arrowsize.split(`,`)[1];
}
},
props: [`list`, `autoplay`, `type`, `time`, `sildetype`, `arrowurl`, `arrowsize`, `option`, `direction`],
methods: {
//滑動操作
switchDo(reduce){
clearInterval(this.timer);
//根據reduce判斷this.nowIndex的增加或者減少!
//如果是減少模式reduce=‘reduce’
if (reduce === `reduce`) {
//如果nowIndex等於0,已經是第一個了,就回到最後一個
if (this.nowIndex === 0) {
this.nowIndex = this.list.length - 1;
}
else {
this.nowIndex--;
}
}
//如果是增加模式reduce=undefined
else {
//如果nowIndex等於this.list.length-1,已經是最後一個了,就回到第一個
if (this.nowIndex === this.list.length-1) {
this.nowIndex = 0;
}
else{
this.nowIndex++;
}
}
//如果需要自動播放
if (this.autoplay) {
this.autoSwitch();
}
},
//自動播放函式
autoSwitch(){
let time = this.time || 4000;
this.timer = setInterval(() => {
this.switchDo();
}, time);
}
}
}
</script> 複製程式碼
到了這裡,剩下的就只有點選兩個箭頭,執行相應動畫,這個就相對簡單,無非就是呼叫switchDo函式,唯一區別在於,點選左邊的箭頭,是減少模式,右邊箭頭的增加模式。程式碼如下,很好理解。
<!--判斷是否需要顯示箭頭-->
<div class="switch-arrow" v-if="arrowurl&&arrowsize">
<div :class="{`arrow-left`:direction===`left`,`arrow-top`:direction===`top`}"
:style="{`width`:arrowWidth+`px`,`height`:arrowHeight+`px`,`background`:`url(`+arrowurl+`) no-repeat`,`background-size`:`100%`}" @click.stop="switchDo(`reduce`)"></div>
<div :class="{`arrow-right`:direction===`left`,`arrow-bottom`:direction===`top`}"
:style="{`width`:arrowWidth+`px`,`height`:arrowHeight+`px`,`background`:`url(`+arrowurl+`) no-repeat`,`background-size`:`100%`}" @click.stop="switchDo"></div>
</div>複製程式碼
到了這裡,對互動有強迫症的開發者就受不了了,到了最後一張,再點選右邊箭頭,就會出現下面的情況!
到了第一張,再點選左邊箭頭也是類似的情況,這樣就很不好。理想情況是下面這樣
3-5細節優化
要想做上面的效果,改的地方會比較多,先說下原理吧,到了最後一張,這個時候,再點選右邊箭頭,像淘寶那樣,回到第一張。到了第一張,再點選左邊箭頭類似效果回到最後一張。那麼最後的佈局是這樣
這樣佈局能實現效果,到了最後一張,這個時候,再點選右邊箭頭,像淘寶那樣,回到第一張。就像下面
這個時候,就需要多做一步,滾動到這裡的時候,瞬間拉回去。而且這個拉回去,要把ul的過渡效果transition
去掉,不然就會看到拉回去的過渡效果!同時要改變nowIndex。
1.首先,ul佈局方面
<div class="switch-img-type switch-img-left" v-if="type===`slide`&&direction===`left`">
<!--用tran這個class控制ul是否含有過渡效果,樣式已經寫好-->
<ul :style="{`width`:ulWidth,`transform`:`translate3d(-`+(listWidth*(nowIndex+1))+`%,0,0)`,`transition-timing-function`:slideChange}"
:class="{`tran`:noLast}">
<!--最後一張圖片-->
<li :style="{`width`:listWidth+`%`}">
<a :href="list[list.length-1].href?list[list.length-1].href:`javascript:;`">
<img :src="list[list.length-1].src" class="slider-img"/>
</a>
</li>
<!--遍歷出來的圖片-->
<li v-for="(li,index) in list" :style="{`width`:listWidth+`%`}">
<a :href="li.href?li.href:`javascript:;`">
<img :src="li.src" class="slider-img"/>
</a>
</li>
<!--第一張圖片-->
<li :style="{`width`:listWidth+`%`}">
<a :href="list[0].href?list[0].href:`javascript:;`">
<img :src="list[0].src" class="slider-img"/>
</a>
</li>
</ul>
</div>複製程式碼
2.然後,對應的點修改
<!--isLast:隱藏最後一個span,isFirst隱藏第一個span-->
<div class="switch-option" v-if="option"
:class="{`isLast`:nowIndex===list.length, `isFirst`:nowIndex===-1,`switch-option-top`:direction===`top`}">
<div>
<span class="active span1" v-if="nowIndex===list.length"></span>
<span v-for="(li,index) in list" :class="{`active`:index===nowIndex}"></span>
<span class="active span2" v-if="nowIndex===-1"></span>
</div>
</div>複製程式碼
這個可能會有點繞,我解釋下,比如滾動最後一張了,再點選右邊箭頭,向右滑動到第一張的時候,如下圖
這個時候又要把第一個點變成藍色,但是對應點的索引和nowIndex對不上,這個時候用一個技巧。把第一個(.span1)點顯示出來,然後把最後一個點隱藏。這樣還是使用者看到還是看到4個點在螢幕!等動畫執行完了,拉回去第一張的時候。把.span1隱藏,正常顯示對應的點!這個大家細想一下就知道了。到了第一張,再點選左邊箭頭類似效果回到最後一張也是相同的處理方式!
到這裡,功能就基本完成了,下面給出這部分程式碼!
<template>
<div class="ec-slide-img-box" id="ec-slide-box">
<div class="ec-slide-img-type ec-slide-img-left" v-if="type===`slide`&&direction===`left`">
<!--用tran這個class控制ul是否含有過渡效果,樣式已經寫好-->
<ul :style="{`width`:ulWidth,`transform`:`translate3d(-`+(listWidth*(nowIndex+1))+`%,0,0)`,`transition-timing-function`:slideChange}"
:class="{`tran`:noLast}">
<!--最後一張圖片-->
<li :style="{`width`:listWidth+`%`}">
<a :href="list[list.length-1].href?list[list.length-1].href:`javascript:;`">
<img :src="list[list.length-1].src" class="slider-img"/>
</a>
</li>
<!--遍歷出來的圖片-->
<li v-for="(li,index) in list" :style="{`width`:listWidth+`%`}">
<a :href="li.href?li.href:`javascript:;`">
<img :src="li.src" class="slider-img"/>
</a>
</li>
<!--第一張圖片-->
<li :style="{`width`:listWidth+`%`}">
<a :href="list[0].href?list[0].href:`javascript:;`">
<img :src="list[0].src" class="slider-img"/>
</a>
</li>
</ul>
</div>
<!--isLast:隱藏最後一個span,isFirst隱藏第一個span-->
<div class="ec-slide-option" v-if="option"
:class="{`isLast`:nowIndex===list.length, `isFirst`:nowIndex===-1,`ec-slide-option-top`:direction===`top`}">
<div>
<span class="active" v-if="nowIndex===list.length"></span>
<span v-for="(li,index) in list" :class="{`active`:index===nowIndex}"></span>
<span class="active" v-if="nowIndex===-1"></span>
</div>
</div>
<div class="ec-slide-arrow" v-if="arrowurl&&arrowsize">
<div :class="{`arrow-left`:direction===`left`,`arrow-top`:direction===`top`}"
:style="{`width`:arrowWidth+`px`,`height`:arrowHeight+`px`,`background`:`url(`+arrowurl+`) no-repeat`,`background-size`:`100%`}"
@click.stop="switchDo(`reduce`)"></div>
<div :class="{`arrow-right`:direction===`left`,`arrow-bottom`:direction===`top`}"
:style="{`width`:arrowWidth+`px`,`height`:arrowHeight+`px`,`background`:`url(`+arrowurl+`) no-repeat`,`background-size`:`100%`}"
@click.stop="switchDo"></div>
</div>
</div>
</template>
<script type="text/javascript">
export default {
data () {
return {
nowIndex: 0,
noLast: true,
timer: null,
slideChange: ``,
arrowWidth: ``,
arrowHeight: ``
}
},
computed: {
ulWidth: function () {
return (this.list.length + 2) + "00%";
},
listWidth: function () {
return 100 / (this.list.length + 2)
}
},
mounted(){
if (this.autoplay) {
this.autoSwitch();
}
this.slideChange = this.sildetype || `ease`;
if (this.arrowsize && this.arrowurl) {
this.arrowWidth = this.arrowsize.split(`,`)[0];
this.arrowHeight = this.arrowsize.split(`,`)[1];
}
},
props: [`list`, `autoplay`, `type`, `time`, `sildetype`, `arrowurl`, `arrowsize`, `option`, `direction`],
methods: {
//滑動操作
switchDo(reduce){
clearInterval(this.timer);
//根據reduce判斷this.nowIndex的增加或者減少!
if (reduce === `reduce`) {
if (this.nowIndex === 0) {
//如果是滑動切換
this.nowIndex--;
//執行完了這次動畫之後,去除過渡效果
setTimeout(() => {
this.nowIndex = this.list.length - 1;
this.noLast = false;
}, 400)
}
else {
this.nowIndex--;
}
}
else {
this.nowIndex++;
}
if (this.nowIndex === this.list.length) {
//執行完了這次動畫之後,去除過渡效果
setTimeout(() => {
this.nowIndex = 0;
this.noLast = false;
}, 400)
}
//如果需要自動播放
if (this.autoplay) {
this.autoSwitch();
}
//如果是滑動切換,設定this.noLast,增加過渡效果
this.noLast = true;
},
//自動播放函式
autoSwitch(){
let time = this.time || 4000;
this.timer = setInterval(() => {
this.switchDo();
}, time);
}
}
}
</script>
<style lang="scss">
.ec-slide-img-box {
width: 100%;
height: 100%;
position: relative;
touch-action: none;
}
.ec-slide-img-type {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
&.ec-slide-img-top {
}
&.ec-slide-img-left {
li {
display: inline-block;
font-size: 0;
}
}
&.ec-slide-img-transparent {
li {
opacity: 0;
transition: opacity 1s;
width: 0;
&.cur {
width: auto;
}
&.show {
opacity: 1;
}
}
}
ul {
font-size: 0;
&.tran {
transition: all .4s;
}
li {
text-align: center;
}
img {
vertical-align: middle;
max-width: 100%;
max-height: 100%;
}
}
}
.ec-slide-arrow {
div {
position: absolute;
z-index: 2;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
opacity: .5;
&:hover {
opacity: 1;
}
&.arrow-left {
left: 10px;
right: auto;
}
&.arrow-right {
right: 10px;
left: auto;
transform: rotate(180deg);
}
&.arrow-top {
top: 10px;
bottom: auto;
}
&.arrow-bottom {
bottom: 10px;
top: auto;
transform: rotate(180deg);
}
}
}
.ec-slide-option {
position: absolute;
font-size: 0;
bottom: 10px;
text-align: center;
width: 100%;
z-index: 5;
&.isFirst {
span:first-child {
display: none;
}
}
&.isLast {
span:last-child {
display: none;
}
}
span {
border-radius: 100%;
margin: 0 5px;
background: #fff;
display: inline-block;
width: 10px;
height: 10px;
&.active {
background: #09f;
}
}
&.ec-slide-option-top {
display: table;
width: 10px;
height: 100%;
top: 0;
right: 10px;
margin: auto;
bottom: 0;
span {
margin: 5px 0;
}
div {
display: table-cell;
vertical-align: middle;
}
}
}
</style>
複製程式碼
3-6其它切換方式
碼農怎麼會滿足於現狀,只有一種切換方式,怎麼行,所以我又完善了些:
1.一個透明度的切換方式。
2.當傳進的list長度為1的時候只顯示圖片,不進行任何動畫。
3.手機的左右滑動事件的處理(不規範處理)!雖然也是很少功能,但是我在日常開發可以滿足!
4.增加上下輪播方式。
完整程式碼有點多,會導致篇幅過長,在這裡不貼了,大家上github看吧!ec-slider
4.小結
好了,今天的開發就到此為止了。起初這個專案我是打算當練手用的,但是後來在專案上使用了,雖然這個寫得比較簡單,但是效果還不錯。現在情況還不是很好,以後有需要也會維護。目前來說,也是建議大家可以玩下這個專案,雖然文章有點長,但是直接看下,邊動手寫程式碼,邊看文章,會發現。一下子就看完了!這個應該是不錯的練手專案,可以熟悉使用vue開發元件!最後,如果大家覺得有哪裡寫錯了,寫得不好,歡迎指點!
————————-華麗的分割線——————–
想了解更多,關注關注我的微信公眾號:守候書閣