馬蜂窩旅遊小程序

前言

前段時間學習了關於微信小程序的開發,光說不練假把式,因此就打算本身手擼一個微信小程序,而網上電商類小程序太多了,因此就選擇了旅遊攻略類小程序來練手。這是我第一次寫小程序和第一次寫文章,不足之處請多包涵,謝謝。下面我會分享我在寫小程序的時候遇到的問題和得到的經驗,但願能給你帶來幫助,也歡迎大佬指正。最後,我要感謝我在寫小程序的時候給我幫助的老師和同窗,還有百度上全部給過我幫助的有名的無名的做者。個人廢話說完了,先上項目效果圖。 php


開發前的準備


項目的全部頁面

自定義頂部導航欄組件

微信小程序自帶的 頂部導航欄 知足不了實際的需求因此就本身寫了一個組件,頂部導航欄要想使用 自定義組件(連接中詳細介紹了關於自定義組件的使用方法)必須把app.json中window屬性設置爲:

"window": {
    "navigationBarTextStyle": "black",//導航欄標題顏色,僅支持 black / white
    "navigationStyle": "custom" //導航欄樣式,僅支持如下值:default 默認樣式custom 自定義導航欄,只保留右上角膠囊按鈕
  }
複製代碼

wxmlcss

<view class='nav-wrap' style='height: {{height*2 + 20}}px; background-color:{{navbarData.backgroundColor}};opacity:{{navbarData.opacity}}'>
  <view style="width:100%;height:100%;">
    <!--城市名-->
    <navigator url="/pages/destination/destination" hover-class="none">
      <view class="nav-city" style='margin-top:{{height*2 + 20-36}}px;' wx:if='{{navbarData.showMain}}'>
        <text>{{navbarData.cityName}}</text>
        <view class="downtips"></view>
      </view>
    </navigator>
    <navigator url="/pages/search/search" hover-class="none">
    <!--搜索框-->
    <view class="section" style='top:{{height*2 + 20-34}}px;' wx:if='{{navbarData.showMain}}'>
      // 這裏的搜索框不是一個input組件,只是一個view可供點擊而後跳到搜索頁
      <view class='search_icon'>
        <icon type='search' size='14px'></icon>
      </view>
      <view class='placehold'>搜索目的地/景點/攻略</view>
    </view>
    </navigator>
  </view>
  <!-- 標題 -->
  <view wx:if="{{navbarData.title!=''}}" class='nav-title' style='line-height: {{height*2 + 44}}px;'>
    {{navbarData.title}}
  </view>
  <!-- 返回上一級按鈕 和 返回主頁按鈕-->
  <block wx:if="{{navbarData.showCapsule===1}}">
    <view class='nav'>
      <view class='nav_back' bindtap="_navback">
        <image src='/images/back.png'></image>
      </view>
      <view class="line"></view>
        <view class='nav_home' bindtap="_backhome">
          <image src='/images/home.png'></image>
        </view>
    </view>
  </block>
</view>
複製代碼

組件中的元素均可以經過當前頁面傳入組件的數據控制顯示與否html

js就寫了兩個 路由跳轉 函數,微信小程序官方文檔有很詳細的介紹,這裏就很少贅述了。git


登陸界面

初進小程序,會跳到登陸受權頁面,由於微信小程序再也不支持 wx.getUserInfo 接口直接彈出受權框的開發方式,因此這裏直接使用 button 組件,並將 open-type 指定爲 getUserInfo 類型,獲取用戶基本信息。

<button style='background:green; color:#fff' open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">贊成受權</button
複製代碼

小程序在受權容許訪問用戶信息後,又會彈出位置受權框用來獲取用戶當前所在地,來渲染主頁面的數據。調用小程序給的接口 wx.getLocation(須要用戶受權) 來獲取經緯度,再把獲取到的經緯度利用 百度地圖開放平臺 提供給小程序使用的API來獲取當前城市的名字,並將城市名字放入緩存,好讓主頁面獲取到。github

##注意: 使用wx.getLocation()須要在app.json中配置正則表達式

"permission": {
    "scope.userLocation": {
      "desc": "小程序將獲取你的位置信息"
    }
  }
複製代碼

登陸界面jsjson

// miniprogram/pages/login/login.js
const app = getApp()
Page({

  /**
   * 頁面的初始數據
   */
  data: {
    show: false,
    // 頂部導航欄數據
    navbarData: {
      showCapsule: 0, //是否顯示左上角圖標   1表示顯示    0表示不顯示
      title: '馬蜂窩旅遊', //導航欄 中間的標題
      backgroundColor: '#354a98', //'#354a98'
      opacity: 1,
      showMain: 0,
    },
    // 此頁面 頁面內容距最頂部的距離
    height: app.globalData.height * 2 + 20,
  },
  bindGetUserInfo(res) {
    let that =this
    let info = res;
    if (info.detail.userInfo) {
      wx.login({
        success: function (res) {
          that.getPlaceData()
        }
      })
    }
  },
  /**
   * 生命週期函數--監聽頁面加載
   */
  onLoad: function (options) {
    let that = this;
    //頁面加載時判斷用戶是否受權過,若是受權過直接跳到主頁面,沒有就顯示受權按鈕
    wx.getUserInfo({
      success: function (res) {
        wx.switchTab({
          url: '/pages/main/index'
        })
      },
      fail(err) {
        that.setData({
          show: true
        })
      }
    })
  },
  // 獲取城市名字
  getCityName(location) {
    return new Promise((resolve, reject) => {
      let that = this;
      var e = {
        coord_type: "gcj02",
        output: "json",
        pois: 0,
        ak: '',//放上本身的ak密鑰 密鑰申請見上文百度地圖開方平臺連接
        sn: "",
        timestamp: ""
      };
      e.location = location;
      wx.request({
        url: "https://api.map.baidu.com/geocoder/v2/",
        data: e,
        header: {
          "content-type": "application/json"
        },
        method: "GET",
        success: function (t) {
          let currentCity = t.data.result.addressComponent.city;
          if (currentCity.slice(currentCity.length - 1) == "市") {
            currentCity = currentCity.slice(0, currentCity.length - 1)
          }
          wx.setStorageSync('currentCity', currentCity)
          resolve(currentCity) //經過城市名字 請求城市數據
        }
      })
    })
  },
  // 獲取經緯度
  getLocation() {
    return new Promise((resolve, reject) => {
      wx.getLocation({
        type: 'wgs84',
        success(res) {
          const latitude = res.latitude
          const longitude = res.longitude
          let location = latitude + ',' + longitude
          console.log(location)
          resolve(location) //獲取城市名字
        }
      })
    })
  },
  getPlaceData() { // 獲取地理信息
    let that = this
    this.getLocation().then((val) => {
      return that.getCityName(val)
    }).then(()=>{
      wx.switchTab({
        url: '/pages/main/index'
      })
    })
  }
})
複製代碼

主頁面

寫小程序的時候我不知道主頁面有兩種樣式,等我知道的時候已經寫了很多東西了,因此就沒有寫成組件了,代碼看起來就很冗長,這是個人失誤(MangFu),但願你在想好寫什麼小程序的時候,必定要把小程序的頁面結構想好來不然就會和我同樣,要改的話就要改不少地方。小程序

  • 普通城市頁面

  • 熱門城市頁面

進入主頁是,頁面會先獲取到緩存中的城市名字,再經過城市名字去請求數據,再根據請求到的數據中的ishot屬性,若是ishot屬性爲真,就顯示熱門城市的頁面 ,反之就顯示普通城市的頁面微信小程序


‘個人’頁面

‘個人’頁面中主要是爲了顯示用戶收藏的內容


景點詳情頁

由於種種緣由(lan)頁面中的大半數據沒有放到Easy Mock裏,馬蜂窩原本就以大數據出名,數據ttm多了。api


洲/國家/城市列表頁

這個頁面的佈局分爲三部分,頭部搜索框用絕對定位定死、左部各大洲的列表用絕對定位定死,右部各大洲的國家是一個微信小程序自帶的組件scroll-view

wxml

<!-- pages/destination/destination.wxml -->
<nav-bar navbar-data='{{navbarData}}'></nav-bar>
<view class="destination" style='top: {{height}}px'>
<!--頭部-->
  <view class="des_head">
  <navigator url="/pages/search/search" hover-class="none">
    <view class="des_search">
      <view class="des_search_icon">
        <icon type='search' size='30rpx' color="#000000"></icon>
      </view>
      搜索目的地
    </view>
  </navigator>
  </view>
  <!--左部-->
  <view class="des_continents">
    <view class="des_continent {{curIndex===index?'add':''}}}" wx:for="{{continents}}" wx:for-item="continent" wx:key='{{index}}' data-index='{{index}}' bindtap="switch_des">
      <view class='des_continent_name {{curIndex===index?"on":""}}}'>{{continent.name}}</view>
    </view>
  </view>
  <!--右部-->
  <scroll-view class='des_cities' scroll-y>
    <block wx:if="{{curIndex==0}}">
      <view class="des_cities_content" wx:for="{{continents[curIndex].cities}}" wx:key="{{index}}" wx:for-item="des_city">
        <view class="des_cities_title">{{des_city.title}}</view>
        <view class="des_city" wx:for="{{des_city.city}}" wx:key="{{index}}" bindtap='goMain' data-city_name="{{item.city_name}}">
          {{item.city_name}}
        </view>
      </view>
    </block>
    <block wx:else>
      <view class="des_area" wx:for="{{continents[curIndex].cities}}" wx:key="{{index}}" wx:for-item="des_city" bindtap='goMain' data-city_name="{{des_city.city_name}}">
          <view class="des_img">
            <image src="{{des_city.img}}" />
          </view>
          <view class="des_city_name">{{des_city.city_name}}</view>
        </view>
    </block>
  </scroll-view>
</view>

複製代碼

js

// pages/destination/destination.js
const app = getApp()
Page({

  /**
   * 頁面的初始數據
   */
  data: {
  <!--頂部導航欄數據-->
    navbarData: {
      showCapsule: 1, //是否顯示左上角圖標   1表示顯示    0表示不顯示
      title: '目的地切換', //導航欄 中間的標題
      backgroundColor: '#fff',//背景顏色
      showMain: 0 ///顯示搜索框
    },
    height: app.globalData.height * 2 + 20,
    continents: [],
    curIndex: 0 //當前洲的索引值
  },
  <!--左部各大洲的點擊事件,來改變右邊顯示的內容,而且改變自身樣式-->
  switch_des(e) {
    let curIndex = e.currentTarget.dataset.index;
    this.setData({
      curIndex,
    })
  },
  <!--右部國家/城市的點擊事件,獲取點擊的元素上綁定的國家/城市的名字,放入緩存,並跳轉到主頁-->
  goMain(e){
    const city_name = e.currentTarget.dataset.city_name;
    wx.setStorageSync('currentCity', city_name)
    wx.switchTab({
      url: '/pages/main/index'
    })
  },
  /**
   * 生命週期函數--監聽頁面加載
   */
  onLoad: function (options) {
    let that = this
    <!--請求數據-->
    wx.request({
      url: 'https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/continents',
      success:(res)=>{
        that.setData({
          continents: res.data.continents
        })
      }
    })
  }
}
複製代碼

搜索頁


實現的功能

點擊切換列表

以主頁爲例

其實全部的切換列表功能都差很少,實現方法就是在被點擊元素上設置一個 自定義屬性 ( data-* ) 爲惟一索引值,用 bind-tap 綁定一個點擊事件,經過點擊事件獲取這個惟一索引值,再經過惟一索引值去數據源找到想要的內容,而後經過數據控制頁面上顯示的內容,在data數據源中設置一個數據如mcurIndex,表示當前選擇的元素,用來區別於其餘元素,顯示不一樣的樣式。

wxml

<view class='menu_list'>
    <!-- {{mcurIndex===index?"on":""}} 表示若是自身的索引值爲當前選擇的元素索引值時,添加一個類名‘on’-->
    <view class='list {{mcurIndex===index?"on":""}}' wx:for="{{placeData.allGuide}}" data-mindex="{{index}}" bindtap='selected_menu' wx:key="{{index}}">
        {{item.name}}
    </view>
</view>
複製代碼

js

selected_menu(e) {
    this.setData({
      mcurIndex: e.target.dataset.mindex,
      size: 0,
      showend: false
    })
    <!--調用本身寫的函數來獲取要顯示的內容的數據-->
    this.bitiyan()
  }
複製代碼

滑動頁面改變頂部導航欄的可見度和上拉加載

  • 滑動頁面改變頂部導航欄的可見度

    以主頁爲例

這裏的實現方法是使用 scroll-view 組件,組件中有個 bindscroll 屬性,會在頁面滾動時觸發bindscroll 綁定的事件還會給函數傳遞一個對象event,其中的scrollTop屬性是咱們須要的,根據scrollTop知道頁面滾動了多少,而後動態設置要傳給組件的數據裏的 opacity 屬性。

<scroll-view class="main_scro" scroll-y bindscroll="scroll" bindscrolltolower="bindDownLoad">
</scroll-view>
複製代碼

js

scroll(e) {
    let opacity = 0;
    if (e.detail.scrollTop < 60) {
      opacity = (e.detail.scrollTop / 100).toFixed(1);
    } else {
      opacity = 1;
    }
    this.data.navbarData.opacity = opacity;
    if (e.detail.scrollTop<10){
      this.setData({
        shownav: false
      })
    }else{
      this.setData({
        shownav: true
      })
    }
    this.setData({
      navbarData: this.data.navbarData,
    })
  }
複製代碼
  • 上拉加載

    以主頁爲例

這裏的實現方法在 scroll-view 組件中加 bindscrolltolower 屬性,會在頁面觸底時觸發bindscrolltolower 綁定的事件。

<scroll-view class="main_scro" scroll-y bindscroll="scroll" bindscrolltolower="bindDownLoad">
</scroll-view>
複製代碼
bindDownLoad() {
    let part = 0; //已經顯示的數據長度
    let all = 0; //總的數據長度
    <!--判斷當前城市是否爲熱門城市-->
    if (this.data.ishot) {
      // 待完善 由於效果相同就沒寫了
    } else {
      if (this.data.mcurIndex === 0) {
        part = this.data.cur_view.length * 2;
        all = this.data.placeData.allGuide[this.data.mcurIndex].content[this.data.hlcurIndex].content.length;
      } else {
        part = this.data.cur_view.length;
        all = this.data.placeData.allGuide[this.data.mcurIndex].content.length;
      }

      if (part < all) {
        wx.showLoading({
          title: '正在加載'
        })
        setTimeout(() => {
          this.bitiyan(this.data.placeData)
          wx.hideLoading()
        }, 1000)
      } else {
        <!--當全部數據都加載完了,就顯示end 圖標-->
        this.setData({
          showend: true
        })
      }
    }
  }
複製代碼

關於scroll-view組件有幾點須要注意的

  • 設置豎向滾動的時後必定要設高度,有時候會發現設置了高度100%後,當滑到底部的時候,會顯示不完整,這時候要看下你是否設置了margin/padding,或者父元素設置了margin/padding,這時的scroll-view組件的高度就要減去相應的margin/padding
  • 當設置爲橫向滾動時須要注意,scroll-view 中須要滑動的元素不能夠用 float 浮動;scroll-view 中的包裹須要滑動的元素的大盒子用 display:flex 是沒有做用的;scroll-view 中的須要滑動的元素要用 dislay:inline-block 進行元素的橫向編排;包裹 scroll-view 的大盒子有明確的寬和加上樣式--> overflow:hidden;white-space:nowrap;

收藏功能

收藏功能我是寫在一個組件裏,原本是想和頂部組件同樣,供多個頁面使用,後來由於寫的頁面中就只有一個有用到這個組件,這裏就不單獨說明這個組件了,並且這個組件和頂部組件基本差很少。

收藏功能的實現,當點擊某一個景點時會觸發點擊事件,相信你看了列表切換功能,已經知道了 bind-tap 的使用方法,這裏就不重複了。這裏就是獲取元素上的自定義屬性,經過路由傳參的方法傳給詳情頁,詳情頁根據傳遞過來的數據,去數據源裏獲取相應的數據,再將數據傳遞給組件,當點擊詳情頁上的收藏按鈕時,會觸發綁定的事件,而後會更新緩存中的collectData收藏夾數據。‘個人’頁面會顯示收藏夾中的數據

詳情頁js

<!--生命週期函數,監聽頁面加載-->
onLoad: function(options) {
    <!--options中包含了傳遞過來的參數-->
    let name = options.name;
    this.getinfo(name)
},
<!--經過名字獲取想要的數據-->
getinfo(name){
    <!--先獲取緩存中已經存在的收藏夾數據,若是不存在就將collectData設爲空數組-->
    let collectData = wx.getStorageSync('collectData') || [];
    if (collectData.filter(e => e.name === name).length > 0) {
      this.setData({
        placeData: collectData.filter(e => e.name === name)[0]
      })
    } else {
      let placeData = wx.getStorageSync('placeData')
      let view = placeData.allGuide[0].content.map(e => e.content)
      let newView = []
      for (let i = 0; i < view.length; i++) {
        newView.push(...view[i])
      }
      this.setData({
        placeData: newView.find(e => e.name === name)
      })
    }
    this.setBottom();
  },
  <!--設置要傳遞給bottom組件的數據-->
  setBottom(){
    this.data.bottomData.placeData = this.data.placeData;
    let bottomData = this.data.bottomData;
    this.setData({
      bottomData
    })
  }
複製代碼

bottom組件的js

// components/bottom/bottom.js
const app = getApp()
Component({
  /**
   * 組件的屬性列表
   */
  properties: {
    bottomData: {   //   由父頁面傳遞的數據,變量名字自命名
      type: Object,
      value: {},
      observer: function (newVal, oldVal) {
       }
    }
  },

  /**
   * 組件的初始數據
   */
  data: {
    height: ''
  },
  attached: function () {
    // 獲取是不是經過分享進入的小程序
    this.setData({
      share: app.globalData.share
    })
    // 定義導航欄的高度   方便對齊
    this.setData({
      height: app.globalData.height
    })
  },
  /**
   * 組件的方法列表
   */
  methods: {
    <!--點擊收藏按鈕觸發的事件-->
    collected(){
      <!--將isCollect(是否收藏過),collectors(收藏人數)從數據中解構出來-->
      let {isCollect,collectors} = this.data.bottomData.placeData;
      isCollect = !isCollect;
      this.data.bottomData.placeData.isCollect = isCollect;
      let collectData = wx.getStorageSync('collectData') || [];
      if(isCollect){
        wx.showToast({
          title: '收藏成功',
          icon: 'success',
          duration: 2000
        })
        collectors++;
        collectData.push(this.data.bottomData.placeData);
      }else{
        wx.showToast({
          title: '已取消收藏',
          icon: 'success',
          duration: 2000
        })
        collectors--;
        collectData = collectData.filter(e => e.name != this.data.bottomData.placeData.name)
      }
      this.data.bottomData.placeData.collectors = collectors;
      <!--將收藏夾數據放入緩存-->
      wx.setStorageSync('collectData', collectData)
      let bottomData = this.data.bottomData;
      this.setData({
        bottomData
      })
    }
  }
})

複製代碼

搜索功能

效果一

效果二

搜索功能的實現是經過原生組件 input 上的bindinput屬性,當鍵盤輸入時觸發bindinput屬性綁定的方法,實時獲取中輸入的值,而後將獲取到的值放入請求地址中請求數據,再將請求得到的數據放入頁面的data數據源中,當請求到的數據不爲空時,頁面上會顯示獲得的全部相關數據,如效果一。當按下搜索按鈕時會觸發input框上bindconfirm屬性綁定的事件,此時頁面上會顯示請求到的數據中的第一條,如效果二。

wxml

<input style='width:500rpx' bindconfirm='confirm' confirm-type='search' focus='true' placeholder="搜索目的地/景點/攻略" bindinput='search'></input>
複製代碼

js

// pages/search/search.js
const app = getApp()
Page({

  /**
   * 頁面的初始數據
   */
  data: {
    navbarData: {
      showCapsule: 1, //是否顯示左上角圖標   1表示顯示    0表示不顯示
      title: '馬蜂窩旅遊', //導航欄 中間的標題
      backgroundColor: '#ffffff', //'#354a98'
      city: '',
      opacity: 1,
      showMain: 0
    },
    height: app.globalData.height * 2 + 20,
    result: [],
    searchparams: '',
    show: true,
    searchHistory: [],
    showResult: false,
    showconfirm: false,
    placedata: []
  },
  <!--清空歷史紀錄-->
  clear() {
    this.setData({
      searchHistory: []
    })
    wx.removeStorageSync('searchHistory')
  },
  <!--當點擊鍵盤上搜索按鈕觸發的事件-->
  confirm(e) {
    if (e.detail.value != '') {
      let searchHistory = wx.getStorageSync('searchHistory') || []
      if (searchHistory.filter(a => a === e.detail.value).length === 0) {
        searchHistory.push(e.detail.value)
        wx.setStorageSync('searchHistory', searchHistory)
      }
      if (this.data.result.length > 0) {
        let currentCity = this.data.result[0].name;
        this.getCityDataByName(currentCity);
      }
      this.setData({
        show: false,
        showResult: false,
        showconfirm: true
      })
    }
  },
  <!--跳到主頁面-->
  gotomain(e) {
    wx.setStorageSync('currentCity', e.currentTarget.dataset.name)
    wx.switchTab({
      url: '/pages/main/index',
    })
  },
  <!--點擊歷史紀錄觸發的事件,效果和confirm方法基本相同,不一樣的是confirm是從頁面data中獲取數據,而dosearch是從接口中獲取數據-->
  gosearch(e) {
    let that = this
    wx.request({
      url: `https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/search?name=${e.currentTarget.dataset.name}`,
      success: (res) => {
        if (res.data.data.length > 0) {
          that.getCityDataByName(res.data.data[0].name)
        } else {
          this.setData({
            show: false,
            showResult: false,
            showconfirm: true
          })
        }
      }
    })

  },
  // 經過城市名字 獲取城市數據
  getCityDataByName(cityname) {
    let that = this
    wx.request({
      url: 'https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/china',
      success: (res) => {
        let placedata = [];
        placedata.push(...res.data.data.china.filter(e => e.chName === cityname))
        that.setData({
          placedata,
          show: false,
          showResult: false,
          showconfirm: true
        })
      }
    })
  },
  <!--當鍵盤輸入時觸發的事件-->
  search(e) {
    let that = this
    wx.request({
      url: `https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/search?name=${e.detail.value}`,
      success: (res) => {
        if (res.data.data.length > 0) {
          that.changecolor(res.data.data, e.detail.value)
        } else {
          that.setData({
            result: [],
            searchparams: '',
            showResult: false
          })
        }
      }
    })
  },
  <!--改變名字顏色-->
  changecolor(result, searchparams) {
    for (let j = 0; j < result.length; j++) {
      let i = result[j].name.search(searchparams);
      let left = result[j].name.slice(0, i),
        mid = result[j].name.slice(i, i + searchparams.length),
        right = result[j].name.slice(i + searchparams.length);
      result[j].left = left;
      result[j].mid = mid;
      result[j].right = right;
    }
    this.setData({
      result,
      searchparams,
      show: false,
      showResult: true,
      showconfirm: false
    })
  },
  _navback() {
    wx.navigateBack({
      delta: 1
    })
  },
  /**
   * 生命週期函數--監聽頁面加載
   */
  onLoad: function() {
    <!--獲取緩存中的搜索歷史並放入數據源-->
    let searchHistory = wx.getStorageSync('searchHistory') || []
    this.setData({
      searchHistory
    })
  }
複製代碼

這個API接口是我用 Easy Mock寫的

Easy Mock地址連接

Easy Mock 代碼

{
  "data": function({
    _req
  }) {
    let i = 0,
    <!--數據源_data因爲篇幅緣由就放了一小段數據-->
      _data = [
        {
            name: '亞洲',
            type: '目的地'
          },
          {
            name: '歐洲',
            type: '目的地'
          },
          {
            name: '大洋洲',
            type: '目的地'
          },
          {
            name: '非洲',
            type: '目的地'
          },
          {
            name: '北美洲',
            type: '目的地'
          },
          {
            name: '南美洲',
            type: '目的地'
          },
          {
            name: '南極洲',
            type: '目的地'
          }
      ],
      <!--_req是easymock封裝的對象,_req.query(將查詢參數字符串進行解析並以對象的形式返回,若是沒有查詢參數字字符串則返回一個空對象);-->
      name = _req.query.name;
    if (name != '') {
    <!--當輸入的值不爲空時-->
      let result = [];
      let data = []
      for (let j = 0; j < result.length; j++) {
      <!--eval() 函數可計算某個字符串,並執行其中的的 JavaScript 代碼。這裏主要是爲了給正則表達式動態傳參-->
        if (eval('/' + name + '/').test(result[j].name)) {
          data.push(result[j])
        }
        <!--當查詢到8個匹配項時跳出循環-->
        if (data.length > 8) break;
      }
      return data
    } else {
    <!--當輸入的值爲空時直接返回空數組-->
      return []
    }
  }
}
複製代碼

熱門城市動畫

由於動畫只有6個元素,因此就沒有必要寫成數組遍歷建立了,直接寫6個盒子,給他們的樣式初始化,讓他們到本身的初始位置去。微信小程序提供了建立動畫實例的API wx.createAnimation

wxml

<view class='video a' animation="{{animation1}}" data-index='0' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[0].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video b' animation="{{animation2}}" data-index='1' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[1].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video c' animation="{{animation3}}" data-index='2' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[2].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video d' animation="{{animation4}}" data-index='3' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[3].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video e' animation="{{animation5}}" data-index='4' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[4].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video f' animation="{{animation6}}" data-index='5' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[5].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
複製代碼

wxss

.a{
  opacity: 0.9;
}
.b{
  transform: translate(170rpx,-110rpx) scale(0.8);
  opacity: 0.8;
}
.c{
  transform: translate(210rpx,-250rpx) scale(0.7);
  opacity: 0.7;
}
.d{
  transform: translate(10rpx,-350rpx) scale(0.6);
  opacity: 0.6;
}
.e{
  transform: translate(-250rpx,-290rpx) scale(0.8);
  opacity: 0.5;
}
.f{
  transform: translate(-300rpx,-130rpx) scale(0.9);
  opacity: 0.8;
}
複製代碼

js

// 動畫的運行路線
  translate: function(i) {
    // 獲取屏幕寬度來實現自適應
    let windowwidth = this.data.windowWidth;
    //動畫的運行狀態status[x軸偏移量,y軸偏移量,scale縮放倍數,opacity透明度],也是動畫的運行路線
    let status = [
      [170, -110, 0.8, 0.7],
      [210, -250, 0.7, 0.6],
      [10, -350, 0.6, 0.5],
      [-250, -300, 0.8, 0.7],
      [-300, -130, 0.9, 0.8],
      [0, 0, 1, 0.9]
    ];
    let x = 0,
      y = 0,
      scale = 0,
      opacity = 0;
    for (let j = 0; j < 6; j++) {
      let animationName = 'animation' + (j + 1);
      x = status[(i + j) % 6][0] / 750 * windowwidth;
      y = status[(i + j) % 6][1] / 750 * windowwidth;
      scale = status[(i + j) % 6][2];
      opacity = status[(i + j) % 6][3];
      this.animation.translate(x, y).scale(scale).opacity(opacity).step()
      this.setData({
        [animationName]: this.animation.export()//導出動畫數據傳遞給組件的 animation 屬性
      })
    }
  },
  hotCityAnimation() {
    let i = 0;
    <!--建立動畫實例-->
    this.animation = wx.createAnimation({
      duration: 2000,
      timingFunction: 'ease',
    })
    let that = this
    let anicontrol = this.data.anicontrol
    anicontrol = setInterval(function() {
      that.translate(i)
      if (i == 5) {
        i = -1;
      }
      i++;
    }, 3000)
    this.setData({
      anicontrol
    })
  }
複製代碼

這裏要注意的是,由於這是寫在tabbar頁面的動畫,並且用了setinterval定時器,會按照指定的週期(以毫秒計)來執行註冊的回調函數,意思就是即便你跳轉到別的頁面,動畫依然在運行,當你回到主頁時,動畫就會運行出錯,出現鬼畜,因此要在主頁的onHide周期函數,監聽頁面隱藏時就把定時器給清除了,而且把動畫實例也清除。

onHide: function() {
    let anicontrol = this.data.anicontrol;
    clearInterval(anicontrol)
    this.setData({
      animation1: '',
      animation2: '',
      animation3: '',
      animation4: '',
      animation5: '',
      animation6: ''
    })
  }
複製代碼

關於css

寫這個小程序我沒有用到任何UI框架,這有壞處,也有好處,壞處就是代碼進度賊慢,好處就是本身增長了不少對css的理解。有想用UI框架的可使用 WeUI 。連接裏有詳細的使用方法。


結語

由於時間和精力的緣故,小程序只寫了幾個頁面和小部分功能,在寫項目的過程當中也發現了本身的不少不足,所以吃到了很多苦頭,可是也學到了很多,能夠說痛並快樂着。但願這篇文章可以對打算寫小程序的你有一點幫助。GitHub源碼在這裏,須要自取。

相關文章
相關標籤/搜索