前言
不知道有多少人跟我一樣愛上“知乎”的呢?一直喜歡“知乎”所帶來的使用者體驗效果和一些新穎的資訊傳送。所以在最近的小程式學習中,開始的第一個專案實訓,就是“知乎”了。
開始做了才知道,“知乎”小程式的工程量之大,我這個前端新手不知應該需要多少個工作日,才能把“知乎”完整做成小程式。不過程式設計師的路本就是邊學習邊成長的,學無止境。小人不才,最近做了一些,忍不住先開始一波經驗和問題的分享了。
“知乎”小程式開跑
成果分享
一、首頁
1. tab欄切換
首頁由三個tab項組成,”關注”,“推薦”,“熱榜”,這幾個頁面的切換功能分別設定了“點選切換”和“滑屏切換”:
剛開始做點選事件的時候一股腦門的簡單勁,簡單粗暴,直接給三個tab設定data-index,然後判斷每次事件觸發取到的index值,然後轉到相應內容,”if…elseif…else…”,如果有很多個tab怎麼辦?if…到何年何月啊~喔得天!就覺得自己太low了,一直覺得能以最短的程式碼寫最好的功能就很讓人敬佩,好吧我還是嘗試了一下的:home.js部分程式碼:
switchTab (e) {//tab點選事件
let index = parseInt(e.target.dataset.index)
this.setData({
currentIndex: index
})
},
handleChangeTab (e) {//tab滑屏事件
const p = 33.3
this.setData({
lineStyle: `left: ${p * e.detail.current}%`
})
}
複製程式碼
直接將index值parseInt,賦值到每次轉到的當前tab的index值:currentIndex,數值的比較相對於字串的比較就輕鬆許多了不是嗎?
而後滑屏事件裡自動會引用點選事件中的結果,這個時候不得不又為parseInt點個贊,我只需要將那條“選中線”在每次觸發該事件時自動乘當前的currentIndex,得到它在不同當前tab下的位置值。
沒錯就沒用if…else!用煩了,還好還有點餘地讓我換口味,沒辦法的時候該low還不是得low,這個時候就得安慰自己是‘走走基層’體驗生活了hhhhh
2. 圖片佔位問題
在進行首頁的三個tab頁時,都存在一個問題,頁面中有些分享的文中,插入了圖片,而有些則沒有插入。但在我們的程式碼中,一般初始都是加了這個圖片元素的,至於資料中加不加入圖片,是發文使用者自己的事兒了吧。
但是呢,小程式起步,我就遇到這樣一個問題,不管有沒有加入圖片,頁面上仍然會給這個圖片元素留了空白佔位,導致其它內容全部被擠下去了。
所以我就在想:怎麼能讓這裡有圖片就顯示,沒圖片就不留佔位呢?
解決:
wx:if => 條件渲染
加入wx:if的元素,頁面會自動判斷是否該渲染該元素所包含的程式碼塊,wx:if:”{{繫結的資料}}”,繫結的資料有值時渲染,無值時不渲染。
3. 熱榜:簡便進行多列布局
看到熱榜頁面的樣式就感覺它就像個多行三列的網格,所以撇開了繁瑣的頁面設計與標籤類名間的位置樣式值設定,果斷用了柵格佈局:
home.wxml部分程式碼:
<view class="container-item" wx:for="{{hotList}}" wx:key="{{item.id}}">
<view class="row">
<view class="col">
<view class="col-1">{{item.rank}}</view>
<view class="col-7">
<view class="title">{{item.title}}</view>
<view class="status">{{item.status}}</view>
</view>
<view class="col-4">
<image wx:if="{{item.titleImage}}" class="title-image" src="{{item.titleImage}}"></image>
</view>
</view>
</view>
</view>
複製程式碼
當然,小程式暫時還沒有col-number的固定值,所以直接設定類名並不會發生變化,沒辦法直接像使用api一樣直接使用,所以,就在app.wxss全域性樣式中定義了每個col塊的寬度:
.col>.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12{
overflow: hidden;
}
.col-1{
width: 8.33333333333333%;
}
.col-2{
width: 16.66666666666666%;
}
.col-3{
width: 25%;
}
.col-4{
width: 33.33333333333333%;
}
.col-5{
width: 41.66666666666666%;
}
.col-6{
width: 50%;
}
.col-7{
width: 58.33333333333333%;
}
.col-8{
width: 66.66666666666666%;
}
.col-9{
width: 75%;
}
.col-10{
width: 83.33333333333333%;
}
.col-11{
width: 91.66666666666666%;
}
.col-12{
width: 100%;
}
複製程式碼
這樣後面這個程式中若還想再建這種佈局就能直接簡便地引用了。
當然,這個頁面中,在設定這個之前,還少不了flex佈局的實現,“彈性佈局”真是我這段時間學習css中最喜歡的東西了。相關知識大家可以看看阮一峰老師的文章。
二、“想法”頁
1.header欄fix及滾動fix功能
該介面完成了一個☝️個人覺得平時刷知乎的時候比較妙的使用者體驗,:頭部nav在下滑到一定距離時仍然fix其主要“功能轉到點”在頭部,如果使用者在下滑到比較長距離的時候想要去到頭部的導航,不用重新上滑很長的距離到頂部去實現這個動作。
先看一波效果:
前端框架semantic ui
在這方面的效果就做得好棒,看人家主頁semantic ui。
所以在做這個專案的時候特意也挑了這個效果去實現。
作為一個前端初學者,還是得依靠我們的“主角”scroll-view,
配置項 | 作用 |
---|---|
scroll-top | 設定豎向滾動條位置 |
scroll-y | 允許縱向滾動 |
bindscroll | 滾動時觸發的回撥函式 |
我在這裡用了scroll事件進行兩個小頁面(也就是頁面滾動前與滾動到 一定距離顯示市顯示的不同top nav頂部導航條)的切換,配合小程式的”wx:if else “框架進行條件渲染,當頁面滾動到設定的目標距離時,切換新生成的頂部導航。
thought.wxml部分程式碼:
切換前的nav:
<view wx:if="{{!display}}" class="nav">
<view class="title">想法</view>
<view class="message">
<image class="messageImg" src="{{messageImg}}" wx:if="{{messageImg}}"></image>
<text class="messageTitle" >訊息</text>
</view>
<view class="myThought">
<image class="thoughtImg" src="{{thoughtImg}}" wx:if="{{thoughtImg}}"></image>
<text class="thoughtTitle">我的想法</text>
</view>
</view>
切換後的nav:
<view wx:else class="nav1">
<view class="title">想法</view>
<view class="message">
<image class="messageImg" src="{{messageImg}}" wx:if="{{messageImg}}"></image>
</view>
<view class="myThought">
<image class="thoughtImg" src="{{thoughtImg}}" wx:if="{{thoughtImg}}"></image>
</view>
</view>
複製程式碼
分別給兩個nav設定不同樣式。
scroll事件觸發nav轉換:
Scroll事件接收兩個引數:
1.scroll-top;
2.display屬性
scroll: function(e){
if (e.detail.scrollTop > 200) {
this.setData({
display: true
})
} else {
this.setData({
display: false
})
}
}
複製程式碼
在這個地方就有幾個開始動手時就踩到的坑:
1.使用豎向滾動條時必須為元件設定一個固定高度,否則bindscroll事件無法觸發;
2.scroll-top在設定豎向滾動條位置時,如果設定的值沒有變化,元件不會渲染!
這種功能呢,用我們老師的話來說… 沒錯,就是很有“質感”!
2 .輪播內容
h5寫過輪播的都只有寫過的知道,相對還是比較麻煩的,並沒有一個輪播圖元件,有個ViewPage也需要自己定製,小程式中swiper元件封裝的相對還是方便的,使用方式也相對容易些。
主要屬性:
配置項 | 作用 |
---|---|
indicator-dots | 是否顯示皮膚指示點 |
autoplay | 是否自動切換 |
current | 當前所在頁面的index |
interval | 自動切換時間間隔 |
duration | 滑動動畫時長 |
bindchange | current改變時觸發change事件 |
屬性只需要設定就行了 也可以抽到js檔案的data中進行資料繫結,監聽使用bindchange,在js中做業務處理。
wxml程式碼塊:
<swiper class="swiper" autoplay="true" interval="2000" duration="1000" circular="true" >
<block wx:for="{{discussion}}" wx:for-index="index">
<swiper-item class="discuss">
<image wx:if="{{item.url}}" src="{{item.url}}" class="swiper-url"/>
<view class="discuss-desc">
<view class="discussing">{{item.discussing}}</view>
<view class="desc-title">{{item.title}}</view>
<view class="desc-question"> {{item.descQuestion}}
<!-- <swiper class="desc-question" autoplay="true" interval="3000" duration="1000" vertical="true">
<block wx:for="{{item.descQuestion}}" wx:if="{{item.descQuestion}}">
<swiper-item class="question">
<view class="question-item">{{item.questionItem}}</view>
</swiper-item>
</block>
</swiper> -->
</view>
</view>
</swiper-item>
</block>
</swiper>
複製程式碼
其實這個頁面裡的輪播中,還巢狀了其中一個項的縱向輪播,剛開始我直接在swiper中另巢狀了一個swiper,然後設定verticle=“true”,但只能將外層swiper的輪播切換時間增加,才能給裡面的縱向輪播預留時間顯示出來,所以體驗效果並不好,有知道怎麼做的可以傳授下一下(๑`・ᴗ・´๑)ヾ(●´∇`●)ノ哇~。
三、搜尋頁
1.“回車確定”搜尋內容
每次輸入搜尋內容後,點選回車確定,轉到詳情頁,搜尋歷史欄也隨之新增一條新記錄,同時將記錄儲存到本地快取,頁面重新整理之後仍然存在歷史記錄。
這裡用到了input元件的bindconfirm事件,也就是每次輸入搜尋內容回車確定時觸發的事件,這裡我用wx:for迴圈設定了一個historyRecord陣列,該陣列接收兩個引數,每次輸入內容的id值和recordItem內容,key值id區分不同一行不同內容的歷史記錄,recordItem是輸出在搜尋歷史欄的記錄value:search.wxml
<view class="search-history">
<text class="zhhs">搜尋歷史</text>
<view class="search-history-item" wx:for="{{historyRecord}}" wx:key="item.id">
<image class="search-history-icon" src="/assets/icons/shizhong.png"></image>
<text>{{item.recordItem}}</text>
</view>
</view>
複製程式碼
在進行bindconfirm事件處理上,剛開始就生生踩雷了,剛開始學小程式都得了解的事,我就犯了錯,我們都應該知道,要改變data裡的資料,只能用setData,剛開始我直接在外部使用了this.data.historyRecord.push({id:’’,recordItem:e.detail.value}); 然後就報錯顯示沒有push這個方法,然後我當然就去找度娘問清楚啦,被提示data裡的資料只能用setData改變,然後一敲腦袋,最後就是下面的樣子了:
search.is
bindconfirm: function(e){
console.log(e);
var historyRecord = this.data.historyRecord;
var recordItem = e.detail.value;
historyRecord.unshift({
id:'0',
recordItem: recordItem
});
this.setData({
historyRecord:historyRecord
});
}
複製程式碼
要使用setData之外的方法,就要借用變數來賦值啦,將陣列賦到外部定義的變數,就可以使用setdata之外的方法了。
而該變數本身就是陣列,陣列的方法之多,夠用的了!起先我用的是push()方法,後來又去摸了下知乎搜尋頁,發現歷史記錄的最新記錄都是直接插入首行,好的,陣列方法unshift()滿足你!
2.“點選搜尋結果”搜尋內容
每次輸入搜尋內容後,出現搜尋結果條頁,點選結果條,轉到詳情頁,搜尋歷史欄也隨之新增一條新記錄。
這裡用到了模糊查詢,關鍵字查詢,利用了陣列的filter過濾方法,遍歷資料庫(我這裡暫時用了假資料,後期掌握後端方法之後補上),選出包含輸入詞的相關結果,羅列在結果條目上供選擇。Search.wxml部分程式碼
<view wx:else class="search-like">
<view class="search-like-item" data-param="{{item.text}}" wx:for="{{searchLikeList}}" wx:key="{{index}}" bindtap="turnTo">
<image class="search-like-icon" src="/assets/icons/sousuo.png"></image>
<text>{{item.text}}</text>
<image data-index="{{index}}" class="turn" src="/assets/icons/turn.png"></image>
</view>
</view>
複製程式碼
Search.js部分程式碼
changeSearch (e) {
let value = e.detail.value
if (value === '') {
this.setData({
haveSerachLike: false
})
return
}
let arr = this.data.searchLikeAllList.filter(item => item.text.indexOf(value) > -1)
console.log(arr)
this.setData({
haveSerachLike: true,
searchLikeList: arr,
})
},
turnTo: function(e){
this.saveHistory({
id: 0,
recordItem: e.target.dataset.param
})
wx.navigateTo({
url: '../searchDetail/searchDetail'
})
},
複製程式碼
選擇結果條目上的某條內容後,點選進入詳情頁,同樣,在歷史記錄中新增一條記錄內容,記錄儲存在本地快取中。
3.清除搜尋記錄
效果圖:
既然有了儲存記錄功能,當然也少不了清楚搜尋記錄的功能。感覺自己抱緊了陣列方法的大腿,這裡再次用到過濾filter,先給要賦予清除事件的元素通過設定data - index 的方法來標識要傳遞的值,然後在deleteRecord事件中將歷史記錄中被該事件選中的index值進行過濾,即刪除所傳遞的index值,然後返回過濾後的陣列,即沒被過濾掉的記錄。
Search.wxml部分程式碼:
<view class="search-history">
<text class="zhhs">搜尋歷史</text>
<view class="search-history-item" wx:for="{{historyRecord}}" wx:key="{{index}}">
<image class="search-history-icon" src="/assets/icons/shizhong.png"></image>
<text>{{item.recordItem}}</text>
<image data-index="{{index}}" class="delete" src="/assets/icons/delete.png" bindtap="deleteRecord"></image>
</view>
複製程式碼
search.js部分程式碼:
deleteRecord: function(e){
console.log(e);
let filterArr = this.data.historyRecord.filter((item, index) => {
return index !== e.target.dataset.index
})
this.setData({
historyRecord: filterArr
})
wx.setStorage({
key: 'historyRecord',
data: filterArr
})
},
複製程式碼
4. 熱搜詞
效果圖:
搜尋頁的熱搜詞模組list出來的是最近熱門搜尋,排在第一個的就是最熱搜尋,依次按照熱門程度排序。(1)佈局:
佈局方式採用彈性佈局,熱搜詞橫向排序,並且在設定固定百分比寬度的情況下用了彈性佈局下flex-wrap:wrap;進行超出則換行操作。
(2)功能實現:
在繫結的熱搜詞資料中給它加入hotstatus引數,表示熱度情況,型別為number:
<view class="search-item">
<view class="hot-search-item" wx:for="{{hots}}" wx:key="{{item.id}}">
<view class="hot-item">
<view class="text">
<image class="hot-img" src="{{item.hotImg}}" wx:if="{{item.hotImg}}"></image>
<text>{{item.text}}</text>
</view>
<view class="hot-status" >{{hotStatus}}</view>
</view>
</view>
</view>
複製程式碼
然後在頁面載入事件onload中新增排序方法,檢索熱詞陣列中每個hotstatus值,按從大到小排序排列在“知乎熱搜”塊中:
onLoad: function (options) {
var hots = this.data.hots;
var hots2 = hots.sort((x, y) => y.hotStatus - x.hotStatus);
// reverse()方法會反轉陣列項的順序
// hots.reverse();
console.log(hots2);
this.setData({
hots: hots2
})
複製程式碼
5 .程式碼優化
做了好幾個搜尋頁的功能,發現“儲存歷史記錄並加入本地快取”這個功能在好幾個地方都用到了,每個事件中都寫一遍,程式碼繁瑣,邏輯可讀性略差,所以我將該功能封裝成一個方法,每次需要用到的時候,直接帶著相應引數引用即可:
saveHistory (param) {
let arr = this.data.historyRecord
arr.unshift(param)
wx.setStorage({
key: 'historyRecord',
data: arr
})
this.setData({
historyRecord: arr
})
}
複製程式碼
隨即,上面第一條的bindconfirm事件函式則變為:
bindconfirm: function(e){
console.log(e);
var recordItem = e.detail.value;
this.saveHistory({
id: 0,
recordItem
})
turnTo事件函式:
turnTo: function(e){
this.saveHistory({
id: 0,
recordItem: e.target.dataset.param
})
wx.navigateTo({
url: '../searchDetail/searchDetail'
})
},
複製程式碼
程式碼是不是變得更加簡潔了?邏輯更加清晰了?這就是我們一直追求的“用最短的程式碼寫最棒的功能!”
四、附頁:下拉重新整理上拉載入更多
原生App開發中,下拉重新整理和上拉載入是使用得比較多的一個功能了。
小程式開發中,小程式只提供了下拉重新整理的介面。
Bug & Tip:
- 在滾動 scroll-view 時會阻止頁面回彈,所以在 scroll-view 中滾動,是無法觸發 onPullDownRefresh
- 若要使用下拉重新整理,請使用頁面的滾動,而不是 scroll-view ,這樣也能通過點選頂部狀態列回到頁面頂部,在這裡其實也就說了在使用scroll-view時是不能使用onPullDownRefresh了。
我這裡直接用了scroll-view實現下拉重新整理上拉載入更多,scroll-view有三個event事件:
配置項 | 作用 |
---|---|
bindscrolltoupper | 滾動到頂部觸發的回撥函式 |
bindscrolltolower | 滾動到底部觸發回撥函式 |
bindscroll | 滾動時觸發的回撥函式 |
這裡js程式碼裡面其實就是處理邏輯:
上拉:我們需要在陣列container-list的後面拼接資料和處理請求的頁碼;
首先需要封裝一個獲取頁碼資料的方法
getPage:
getPage: function(){
var that = this;
var pageIndex = that.data.currentPage;
wx.request({
url: '',
data: {
page: pageIndex
},
success: function(res){
if(pageIndex != 1){ // 載入更多
console.log('載入更多');
var tempArray = that.data.articles;
tempArray = tempArray.concat(that.data.articles);
that.setData({
allPages: that.data.allPages,
articles: tempArray,
hideBottom: true
})
}
}
複製程式碼
然後判斷當前頁是否是最後一頁:
if (that.data.currentPage == that.data.allPages){
that.setData({
loadMoreData: '已經到頂'
})
return;
}
複製程式碼
這裡加了一個定時,是為了延長上拉下拉檢視的顯示時間:
setTimeout(function(){
console.log('上拉載入更多');
var currentPage = that.data.currentPage;
currentPage = currentPage + 1;
that.setData({
currentPage: currentPage,
hideBottom: false
})
that.getPage();
},300);
複製程式碼
下拉:我們需要把當前頁碼設定成1,articles取當前網路請求的資料。網路請求getData函式上拉下拉的區分是通過當前頁碼值區分的。
refresh: function(event){
var that = this;
page = 1;
that.setData({
articles: [{這裡加入傳入的重新整理的資料}]
scrollTop: 0,
hidden:true
});
that.getPage();
// GetList(this)
},
複製程式碼
來一波成品圖:
這個功能各網頁、app上都用得很廣,我做得還不太完善,後續慢慢修改完善。結語:
因為時間比較短,知乎也是個大專案,頁面、功能還需要慢慢完善,釋出了的功能也有一些待改進的地方,後續慢慢把這個專案做下去,慢慢打磨技術。愛程式碼,愛知乎! 歡迎同樣志同道合的碼友們多多指教和交流。ヾ(❀╹◡╹)ノ~
順便附上我的專案地址:仿“知乎”微信小程式
icon“贊助地”:icon