preface

I do not know how many people like me love zhihu? I always like the user experience effect and some novel information transmission brought by “Zhihu”. So in the recent small program learning, the first project training, is “zhihu”. Started to do just know, “Zhihu” small program of the large amount of engineering, I do not know how many front-end novice should need working days, to “Zhihu” complete small program. But the way of a programmer is to learn and grow. There is no end to learning. Small people, recently made some, can not help but first started a wave of experience and problems to share.

“Zhihu” small program started running

Results to share

A, the home page

1. Switch the TAB bar

The home page is composed of three TAB items, “attention”, “Recommendation” and “Hot list”. The switching function of these pages is set to “click to switch” and “slide to switch” respectively:

SwitchTab (e) {// TAB click eventlet 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}% `})}Copy the code

I’m just going to assign the index value parseInt to the index value of the current TAB that I’m going to go to every time, currentIndex, so it’s a lot easier to compare values than strings, right? I’ll have to give parseInt a thumbs up. All I need to do is multiply the “checked line” by the currentIndex each time the event is triggered to get its position under a different TAB. That’s right, it doesn’t work if… else! With bored, fortunately there is some room for me to change taste, can not be low when the low is not low, this time have to comfort themselves is’ walk the grassroots’ experience of life HHHHH

2. Picture occupying problem

There was a problem in the three TAB pages of the home page. Some shared articles were inserted with pictures, while others were not. However, in our code, the initial element is usually added to the image, as to whether to add images to the data, it is up to the user. However, as soon as I started the mini program, I ran into a problem that the page would still leave blank space for the image element, regardless of whether the image was added, causing everything else to be crowded out. So I was thinking: how can I make it show if there is a picture, and leave no space if there is no picture?

Wx :if: “{{binding data}}”, binding data has value when rendering, no value does not render.

3. Hot list: simple multi-column layout

The style of the hot list page feels like a grid with multiple rows and three columns, so instead of tedious page design and setting the location style value between the name of the tag class, we decided to use the grid layout:

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>
Copy the code

Of course, the applet does not have a fixed col-number, so setting the name of the class does not change. It cannot be used directly like using the API, so we define the width of each COL block in the app.wxSS global style:

.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%; 2 {}. Col 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%; 11 {}. Col width: 91.66666666666666%; } .col-12{ width: 100%; }Copy the code

So later in the program if you want to build this layout again you can reference it directly and easily. Of course, in this page, before setting this up, there is also the implementation of flex layout, “elastic layout” is really my favorite thing to learn about CSS in a while. Relevant knowledge we can see ruan Yifeng teacher’s article.

2. “Ideas” page

1. Header bar fix and scroll fix function

The interface completed a ☝ ️ personal think brush zhihu at ordinary times more better user experience, : head nav falling to a certain distance still fix the main “transfer function to point” in the head, if the user declines to a more long time want to go to the navigation of the head, don’t have to go on a long distance to the top again to implement the action. Take a look at the first wave of effects:

The semantic UI of the front-end framework does a really good job of that, if you look at the semantic UI of the home page. Therefore, I deliberately chose this effect to achieve when DOING this project. As a front-end beginner, we still have to rely on our “protagonist” Scroll view,

Configuration items role
scroll-top Sets the vertical scroll bar position
scroll-y Allow longitudinal roll
bindscroll The callback function that is triggered when scrolling

Here I use scroll event to switch between two small pages (i.e. the top navigation bar of different top Nav displayed before the page is rolled and when the page is rolled to a certain distance). With the “Wx: If Else” frame of the applet for conditional rendering, when the page is rolled to the set target distance, switch the newly generated top navigation.

WXML: <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"> Message </text> </view> <view class="myThought">
        <image class="thoughtImg" src="{{thoughtImg}}" wx:if="{{thoughtImg}}"></image>
        <text class="thoughtTitle"</text> </view> </view> Toggle 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>
Copy the code

Give each NAV a different style.

The Scroll event triggers the nav conversion: the Scroll event receives two arguments: 1. 2. The display properties

scroll: function(e){
    if (e.detail.scrollTop > 200) {
      this.setData({
        display: true})}else {
      this.setData({
        display: false}}})Copy the code

1. When using the vertical scroll bar, you must set a fixed height for the component, otherwise the BindScroll event will not trigger. 2. When setting the vertical scroll bar position, the component will not render if the value does not change.

This function, in the words of our teachers… Yes, it has texture!

2. Rotating broadcast content

H5 has written round broadcast are only written know, relatively or more trouble, and there is no a round broadcast graph component, there is a ViewPage also need to customize their own, small program swiper component package is relatively convenient, relatively easy to use.

Main attributes:

Configuration items role
indicator-dots Whether to display panel indicator points
autoplay Whether to switch automatically
current Index of the current page
interval Auto switch time interval
duration Slide animation duration
bindchange The change event is triggered when current changes

Properties only need to be set. You can also extract data from JS files for data binding, listen to use Bindchange, and do business processing in JS. WXML code block:

<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>
Copy the code

Search page

1. Press Enter to enter the search content

After entering the search content each time, click Enter to go to the details page, and a new record will be added to the search history bar. At the same time, the record will be saved to the local cache, and the historical record will still exist after the page is refreshed.

search.wxml

<view class="search-history">
            <text class="zhhs"> search history </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>

Copy the code

I made a mistake at the beginning of bindConfirm event processing. We should all know that to change the data in data, we can only use setData, At first I directly on the external use this. Data. HistoryRecord. Push ({id: ‘, ‘recordItem: e.d etail. Value}); Then I got an error showing that there was no push method, and of course I went to Baidu to find out. I was reminded that the data in data can only be changed by setData, and then I hit my head, and finally it looks like the following:

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
    });
  }

Copy the code

To use a method other than setData, you need to borrow a variable to assign. To assign an array to an externally defined variable, you can use a method other than setData. And the variable itself is an array, array of methods, enough! At first I used push (), then I went to the Zhihu search page, and found that the latest records of the history are directly inserted into the first line, ok, array method unshift () satisfied you!

2. “Click search results” to search for content

Every time after entering the search content, the search results page will appear. Click the results to go to the details page, and a new record will be added to the search history bar.

<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>

Copy the code
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'})},Copy the code

After selecting a certain content on the result entry, click to enter the details page, and also add a record content to the history record, which is saved in the local cache.

3. Clear search records

Effect:


<view class= Search. WXML"search-history">
          <text class="zhhs"> search history </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>

Copy the code
Search.js: deleteRecord:function(e){
    console.log(e);
    let filterArr = this.data.historyRecord.filter((item, index) => {
      returnindex ! == e.target.dataset.index }) this.setData({historyRecord: filterArr
    })

    wx.setStorage({
      key: 'historyRecord',
      data: filterArr
    })
  },

Copy the code

4. Hot search term

Effect:











<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>
Copy the code

Then add the sorting method in the page loading event onload, retrieve each hotstatus value in the hotwords array, and arrange them in the “Zhihu hot search” block in order from the largest to the smallest:

onLoad: function(options) { var hots = this.data.hots; var hots2 = hots.sort((x, y) => y.hotStatus - x.hotStatus); // reverse() reverses the order of the items // hots.reverse(); console.log(hots2); this.setData({ hots: hots2 })Copy the code

5. Code optimization

Made several search page function, found that “save history and join a local cache” this function is used in several places, each event to write again, code red, slightly less logic readability, so I’m going to the function in a way, every time needed, directly with the corresponding parameters reference:

saveHistory (param) {
    let arr = this.data.historyRecord
    arr.unshift(param)
    wx.setStorage({
      key: 'historyRecord',
      data: arr
    })
    this.setData({
      historyRecord: arr
    })
  }
Copy the code

The first bindConfirm event function above becomes:

bindconfirm: function(e){ console.log(e); var recordItem = e.detail.value; This.savehistory ({id: 0, recordItem})function(e){
    this.saveHistory({
      id: 0,
      recordItem: e.target.dataset.param
    })
    wx.navigateTo({
      url: '.. /searchDetail/searchDetail'})},Copy the code

Is the code getting cleaner? More logical? That’s what we’re all about: “Write the best feature in the shortest code!”

Iv. Supplementary page: pull down refresh and load more

In native App development, pull-down refresh and pull-up load are one of the most used features. In applets development, applets only provide a drop – down refresh interface. Bug & Tip:

  • Scrolling in the Scroll view will prevent the page from bouncing back, so scrolling in the Scroll view will not trigger onPullDownRefresh
  • To use a pulldown refresh, use the page scroll instead of the scroll view, so that you can go back to the top of the page by clicking on the status bar at the top.

I’m directly using the Scroll view to pull down refresh and load more, and the scroll view has three events:

Configuration items role
bindscrolltoupper Scroll to the callback function triggered at the top
bindscrolltolower Scrolling to the bottom triggers the callback function
bindscroll The callback function that is triggered when scrolling

We need to concatenate the data at the end of the container list and process the page number of the request. You first need to encapsulate a method to get the page number data

Getpages: getpages:function(){
    var that = this;
    var pageIndex = that.data.currentPage;
    wx.request({
      url: ' ',
      data: {
        page: pageIndex
      },
      success: function(res){
        if(pageIndex ! = 1){// Load more console.log('Load more');
          var tempArray = that.data.articles;
          tempArray = tempArray.concat(that.data.articles);
          that.setData({
            allPages: that.data.allPages,
            articles: tempArray,
            hideBottom: true}}})Copy the code

Then determine if the current page is the last:

if (that.data.currentPage == that.data.allPages){
      that.setData({
        loadMoreData: 'Reached the top'
      })
      return;
    }

Copy the code

A timer is added to prolong the display time of the pull-up view:

setTimeout(function(){
      console.log('Pull up to load more');
      var currentPage = that.data.currentPage;
      currentPage = currentPage + 1;
      that.setData({
        currentPage: currentPage,
        hideBottom: false}) that.getPage(); }, 300);Copy the code

Drop down: We need to set the current page number to 1, and articles takes the data from the current network request. The pull-down of the network request getData function is distinguished by the current page value.

refresh: function(event){ var that = this; page = 1; SetData ({articles: [{articles: [{insert the data that was passed in}] scrollTop: 0, hidden:true  
    });  
    that.getPage();
    // GetList(this) 
  },
Copy the code

A wave of finished pictures:

Conclusion:

Due to the short time, Zhihu is also a big project, and the page and functions still need to be improved gradually. There are still some areas to be improved in the released functions. In the future, we will continue to work on this project and polish the technology slowly. Love code, Aizhihu! Welcome the same like-minded code friends more advice and communication. ヾ (❀ ╹ ◡ ╹) ノ ~

By the way, attached is my project address: Icon of wechat mini program imitation of “Zhihu”