文心雕刺

文心雕刺 查看完整檔案

杭州編輯  |  填寫畢業院校  |  填寫所在公司/組織填寫個人主網站
編輯
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 個人簡介什么都沒有

個人動態

文心雕刺 發布了文章 · 2月14日

小程序自定義下拉刷新組件(為應對頭部有非滾動區的情況)

效果動圖:

image

小程序是有其下拉刷新api的,然而頭部或者尾部有非滾動區域的情況下,是應對不了的,相關問題在微信開放社區已經是老生常談了,比如:

onPullDownRefresh 下拉刷新在安卓手機上會帶動頭部fixed元素一起下拉的問題

又比如:

小程序下拉刷新onPullDownRefresh 問題,fixed定位下移!

像這種問題有很多,不一一列舉了。

這個問題從2018年開始就不斷有開發者向微信開放社區反饋,一直到現在,都沒有解決,沒辦法,本公司項目中有頭部是固定非滾動的列表頁面,只得自己自定義一個。

列表頁的上拉加載,參考了這篇文章中的方法,鏈接放在這里,大家可以移步去看一下:

淺談微信小程序中的下拉刷新和上拉加載

回到主題,下拉刷新,我的思路是在這篇文章里找到的:

小程序開發踩坑記錄(五)——模擬實現底部tabbar和下拉刷新功能(解決安卓端打開下拉刷新功能后fixed元素失效問題)

我一上來打算用該文章中的方法,來自制下拉刷新,可是我是在自定義組件中使用,不是在page中定義,在自定義組件中scroll-view的onscroll事件怎么也響應不了,也就無法獲得當前頁面的scrollTop。

后來我受這個啟發,既然可以在tocuh事件中改變scroll-view的scrollTop,來模擬出一個下拉刷新區域的scrollTop隨手指滑動而變化,進而實現其下拉顯示和回彈,那我也可以使用絕對定位和相對定位,依靠改變容器的top值來實現這個功能,也就是可以將滾動容器的top初始值設置為負的刷新區域的高度,在tocuhstart和tocuhmove事件中獲取手指在屏幕上的滑動距離,讓容器的top和滑動距離呈正相關,當top值達到自己設置的閾值的時候停止變化,隨后在tocuhend事件中發起刷新請求。

image

大體思路如上圖所示

下面貼一下基本代碼:


<view
  class="scroll"
  scroll-y
  style="min-height: {{pageHeight + 'px'}}; top: {{marginTop + 'rpx'}}"
  enable-back-to-top="{{true}}"
  bindtouchmove="movePull" 
  bindtouchstart="startPull" 
  bindtouchend="endPull" >
  <view class="scroll__content {{springbacking ? 'return__content' : ''}}" style="min-height: {{contentHeight + 'px'}};top: {{scrollTop + 'px'}}">
    <view class="refresh__wrap">
      <view class="refresh__center">
        <view class="refresh__icon {{refreshText === '松開刷新' ? 'rotate' : ''}}" >
          <mp-icon icon="sending" color="#999" size="{{35}}"></mp-icon>
        </view>
        <view class="tips">{{refreshText}}</view>
        <view class="times">最后更新:{{refreshTime}}</view>
      </view>
    </view>
    <slot />
    <view class="footer__line" wx:if="{{recycleList.length}}">
      <view class="scroll__loading" wx:if="{{bottomLoadingShow}}"></view>
      <text class="text {{bottomLoadingShow ? 'notline' : ''}}">{{lineText}}</text>
    </view>
  </view>
  <no-data 
    show="{{notDataShow}}"
    bind:refresh="refresh" />
</view>

重點說下,這個組件結構為外層.scroll元素,最小高度設置為pageHeight(計算方法后面會貼),position為relative,其top值設置為父級頁面傳入的marginTop值,就是父級頁面頭部非滾動元素的rpx高度,如果沒有非滾動元素,則默認為0,這樣的話,外層的位置就可以依賴marginTop值來確定了,滾動的時候不會把非滾動區域也算進去;
內層列表滾動區域.scroll__content元素,position為absolute最小高度contentHeight,top值scrollTop,初始-80px(刷新文案元素高度),這樣就把.refresh__wrap刷新文案元素隱藏起來了。此外,設置一個默認插槽,就是列表循環內容。

pageHeight和contentHeight計算方法,用到wx.getSystemInfozhege api,比較簡單:

getPageHeight() {
      const self = this;
      wx.getSystemInfo({
        success: (res) => {
          self.setData({
            pageHeight: res.windowHeight - (self.data.marginTop / 750 * res.windowWidth),
            contentHeight: (res.windowHeight - (self.data.marginTop / 750 * res.windowWidth)) + REFRESHHEIGHT + 2,
          })
        }
      })
    }

以上,marginTop是父級組件傳進來的頭部非滾動區域的rpx高度值(頭部非滾動區域position設置為fixed),默認為0,REFRESHHEIGHT是80,就是刷新文案區域的高度px值,最后加2是保證在第一頁鋪不滿整屏的情況下,能讓onReachBottom上拉事件生效,上拉加載本文不多說了,可以直接去看上面貼出的鏈接:“淺談微信小程序中的下拉刷新和上拉加載”(注意,我這里是自定義組件,onReachBottom事件同樣要在父頁面中寫onReachBottom生命周期,來調用自定義組件中的onReachBottom方法)。

tocuhstart方法,就是獲取手指開始接觸屏幕的clentY值:

startPull(ev) {
  this.setData({ springbacking: false }); // 開始下拉的時候,去除.scroll的transition為0.4s的動畫延遲效果,使其完全遵循手指的驅動
  this.lastTop = ev.changedTouches[0].clientY;
},

tocuhmove方法:

// MAX_MOVE_TOP 為 120 允許最大滑動距離
// MAX_SCROLL_TOP 為 20 允許.scroll__content的最大top值
movePull(ev) {
      this.nowY = ev.changedTouches[0].clientY;// 手指當前觸摸位置的clentY值
      this.nowY = this.nowY - this.lastTop;// 滑動距離
      const query = wx.createSelectorQuery();
      query.select('.scroll').boundingClientRect();
      query.selectViewport().scrollOffset();
      query.exec((rect) => { // 必須是滾動高度為0即在頂部的時候觸發
        if (rect[1].scrollTop <= 0 && this.nowY > 0 && this.nowY <= MAX_MOVE_TOP && this.data.recycleList.length) {
          this.setData({// 滿足以上條件的,則使.scroll__content元素的top值等于-80px 加上滑動距離 nowY
            scrollTop: -REFRESHHEIGHT + this.nowY,
          })
          if(this.nowY >= 100) {
            this.setData({
              refreshText: '松開刷新',
            })
          }
        }
      })

      if(this.nowY > MAX_MOVE_TOP && this.data.scrollTop < MAX_SCROLL_TOP && this.data.recycleList.length) {
        this.setData({// 此處判斷是為了解決手指滑動過快,tocuhmove得到的clentY值呈非線性變化,導致滑動距離可能上一次還是100以內,下一次直接就到300開外,無法滿足上面的top變化條件,就卡住了。所以此時手動將.scroll__content的top值設置為20。
          refreshText: '松開刷新',
          scrollTop: MAX_SCROLL_TOP,
        })
      }
      ...
      // 上拉加載邏輯
      ...
    }

以上,下拉刷新都是在進入頁面有數據的情況下才會觸發即this.data.recycleList.length這個條件,無數據的情況下則顯示no-data組件,如下圖所示,直接點擊刷新按鈕刷新即可。
image

tocuhend事件:

endPull() {// 結束滑動的時候當.scroll__content的top值大于等于20,則可以執行刷新方法。
      if(this.data.scrollTop >= MAX_SCROLL_TOP) {
        wx.showNavigationBarLoading();
        this.refresh();
      }
      ...
      // 上拉加載邏輯
      ...
      if (this.data.scrollTop > -REFRESHHEIGHT) {
        setTimeout(() => { // 定時器是防止因為手指離開屏幕過快(類似點擊事件,但又下拉了一段距離),導致的數據更新,視圖未更新,而卡住的情況。
          this.setData({ // 這個springbacking為true的時候,.scroll__content元素的transition就是0.4s,回彈時候的動畫效果。
            springbacking: true,
            scrollTop: -REFRESHHEIGHT
          })
        }, 50)
      }
    },

好了,下拉刷新的邏輯就寫完了,該下拉刷新在安卓和ios上的效果差別不是很大,由于業務需要,我是把他做成了一個組件,一些刷新方法和父頁面的請求事件成功交互,失敗交互,不在本文探討范圍之內,所以就略去了,想試試的朋友可以直接在頁面中將slot插槽替換成列表循環元素嘗試。

查看原文

贊 9 收藏 10 評論 1

文心雕刺 發布了文章 · 2月6日

小程序省市區選擇器,對接公司的地址數據

氣,文章一發出來第二天就被51CTO博客某一個人給抄了去了,也不注明出處作者,抄的連文章里面的鏈接也沒了,真的就是一鍵復制粘貼啊。

公司開始做小程序了,小程序的省市區三級聯動picker組件mode="region",之前也有接觸過,這一次一上來先嘗試了一下,發現不能和之前公司的地址庫結合,因為之前項目都是和后端通過地區編碼來交互的,這個自帶的無法滿足現有的情況。于是用小程序picker組件的多列選擇器自制了一個。
開始主題之前先說一下地址數據的問題,我司的數據也是我自己2020年8月從高德接口導出來的最新數據并同步給后端的:

接口地址:https://restapi.amap.com/v3/c...

高德行政區查詢接口文檔

通過這個接口可以獲取全國的行政區數據,至于拿到數據以后組裝成什么格式,這里不細說了,都是基本功,只要留意著點省直轄縣,市直轄鎮等特殊級別關系的情況。

當然小程序省市區選擇器數據咱們也是有辦法弄到,只是,現有在用的數據庫不宜大動,于是沒有用,傳送門也放在這里吧。

小程序官方地區選擇器數據

當時我有這么兩個格式:一個是樹形的,拿到就可以用:
樹形json

然而一看大小,好家伙,440kb,太大了,用不了。

另一個是平鋪格式的:

image

這個96kb,雖然還是有點大,壓縮一下,勉強可以用了。只是這不是一個樹形結構,所以前期還要準備幾個方法備用。

地區編碼是有其規則的,比如,省級行政區是兩位,后四位是“0000”,地市一級的是四位,后兩位是“00”,到區縣一級則是完整的6位。
于是先把平鋪的json,過濾出省市區三個級別的數組出來。

// region.js
// 這個data就是那個平鋪格式的json
const data = require('./data.js');

const list = []
const province = []
const city = []
const area = []

Object.entries(data).forEach(val => {
  const key = Number.parseInt(val[0])
  const model = { key: val[0], value: val[1] }
  list.push(model)
  if (!(key % 1e4)) {
    province.push(model)
  } else if (!(key % 100)) {
    city.push(model)
  } else {
    const num = Number(val[0].substr(2))
    if (num > 9000) {
      city.push(model)
    } else {
      area.push(model)
    }
  }
})

module.exports = {
  srcList: list,
  srcProvince: province,
  srcCity: city,
  srcArea: area,
}

這樣,省一級的行政區數組就拿到了,地市一級和區縣一級的則是每次根據選擇的上一級的地區編碼,來過濾出其管轄的下一級行政區劃的數組:

// createTree.js
const region  = require('./region');

module.exports = {
  /**
   * load city list by province data
   *
   * @param province: { key: 330000, value: '浙江省' }
   * @returns {Array}
   */
  loadCity (province) {
    if (province && Object.keys(province).length) {
      const list = region.srcCity.filter(val => {
        const num = Number.parseInt(province.key)
        return (val.key - num) < 1e4 && (val.key % num) < 1e4
      })
      // Municipalities directly under the central government
      return list.length ? list : [province]
    } else return []
  },

  /**
   * load area list by city data
   *
   * @param city: { key: 330100, value: '杭州市' }
   * @returns {Array}
   */
  loadArea (city) {
    if (city && Object.keys(city).length) {
      const cityKey = Number.parseInt(city.key)
      const isNotProvince = cityKey % 1e4
      const calcNum = isNotProvince ? 100 : 1e4
      const list = region.srcArea.filter(val => {
        return (val.key - cityKey) < calcNum && val.key % cityKey < calcNum
      })
      // Prefecture-level city
      return list.length ? list : [city]
    } else return []
  },
}

好了,準備工作完畢,進入小程序頁面。

<picker 
  class="picker" 
  mode="multiSelector" 
  bindchange="bindMultiPickerChange" 
  bindcolumnchange="bindMultiPickerColumnChange" 
  bindcancel="cancel"
  value="{{regionIndex}}"
  range="{{regionArray}}"
  range-key="value">
  <mp-cell ext-class="utils__item">
    <text class="text">所在地區</text>
    <input bindinput="inputChange" 
    readonly 
    disabled 
    value="{{areaText}}" 
    class="weui-input" 
    placeholder="所在地區"/>
  </mp-cell>
</picker>

頁面元素結構就是這樣,沒什么好說的,這個可以直接看小程序picker中多列選擇器的文檔。先看一下,頁面用到的相關數據吧。

data: {
    areaText: '', // 顯示的省市區文字
    blockArray: [], // 顯示的省市區三級數組,二維數組
    blockIndex: [0, 0, 0], // 顯示出來的選擇下標,默認,[0,0,0]
    regionArray: [], // 選擇器當前的省市區三級數組,二維數組
    regionIndex: [0, 0, 0], // 選擇器當前選中的下標
    provinceList: srcProvince, // 省級數組,
    cityList: [], // 地市級數組,
    areaList: [], // 區縣數組
}

可以看到,我這里有兩個array和index的數組,這是因為,其中一個保存的是已被選擇的數據,另一個則是當前正在選擇的數據,主要考慮到一個取消功能。你要把之前選中的保存起來,如果點擊了取消按鈕,才能還原為上次選擇的數據。

const  { srcProvince } = require('../../utils/region.js');
const { 
    loadCity, 
    loadArea, 
} =  require('../../utils/createTree.js');

onLoad() { // 初始化數據,這里還涉及到一個數據回顯,比較麻煩,放到后面再說
  let cityList, areaList,
  cityList = loadCity(srcProvince[0]);
  areaList = loadArea(cityList[0]);
  this.setData({
    blockArray: [srcProvince, cityList, areaList],
    regionArray: [srcProvince, cityList, areaList],
    cityList,
    areaList,
  })
},

bindMultiPickerChange (e) { // 點擊確定按鈕的事件
    const oldKey = this.data.blockArray[2][this.data.blockIndex[2]].key; // 這是留住上次選中的第三級區域代碼
    const newKey = this.data.regionArray[2][this.data.regionIndex[2]].key; // 本次選中的第三級區域代碼
    
    if(oldKey !== newKey) { // 如果本次選擇和之前的不一樣,則修改數據
      this.setData({
        blockArray: this.data.regionArray,
        regionIndex: e.detail.value,
        blockIndex: e.detail.value,
        areaText: `${this.data.regionArray[0][this.data.regionIndex[0]].value}${this.data.regionArray[1][this.data.regionIndex[1]].value}${this.data.regionArray[2][this.data.regionIndex[2]].value}`
      })
    }
  },
  
bindMultiPickerColumnChange (e) { // 這是選擇器列數據發生變化的時候,就是選擇器滾動的時候
    const regionIndex = this.data.regionIndex;
    // 這個 column 就是發生變化的是第幾列,0,1,2,value則是滾動下標
    regionIndex[e.detail.column] = e.detail.value;
    // 下面的處理是當選擇上一級的時候,要把下一級下標設置為0,防止下一級下標越界
    if(e.detail.column === 0) {
      regionIndex[2] = 0;
      regionIndex[1] = 0;
    } else if(e.detail.column === 1) {
      regionIndex[2] = 0;
    }
    const provinceItem = srcProvince[regionIndex[0]];
    let cityList = [], areaList = [];
    cityList = loadCity(provinceItem);
    areaList = loadArea(cityList[regionIndex[1]]);

    this.setData({
      regionArray: [srcProvince, cityList, areaList],
      regionIndex: regionIndex,
      cityList,
      areaList,
    })
  },
  
  cancel() { // 點擊取消的時候需要把數據還原回選擇之前的狀態
    this.setData({ 
      regionArray: this.cloneDeep(this.data.blockArray),
      regionIndex: this.cloneDeep(this.data.blockIndex), // 這里需要將數組深拷貝,否則,第二次進入取消下標會亂掉
    });
  },
  
  cloneDeep(initArr) {
    let arr = [];
    arr = JSON.parse(JSON.stringify(initArr));
    return arr;
  }

這個選擇器的邏輯就是以上這些,主要是選擇器列變化的時候要把當前列下一級的行政區劃數組找出來,并且留意用戶取消的情況。

最后再說說數據回顯,現在是沒有數據的時候,就是默認的[0,0,0]的情況,處理很簡單,否則,就是有數據的情況,我們跟后端之間的交互都是通過最后一級的行政編碼來的,就是我這邊給后臺就只有一個areaCode,后端返回也只有一個areaCode。因為這個在無論我們當前項目還是在這個小程序的后臺項目上,邏輯上都不會存在只有省一級或者只有省市兩級的情況,所以。只要有數據,這個areaCode必然是區縣一級的代碼。于是在createTree.js里增加一個方法:

/*
  * areaCodeToMap
  * @param {string} code
  * @param {array} list
  */

  areaCodeToMap(code, list) {
    const result = {
      map: {},
      index: 0,
    };
    list.map((item, i) => {
      if(item.key === code) {
        result.map = item;
        result.index = i;
      }
    })
    return result;
  },

這是通過這個areaCode把上面兩級的行政區對象及其下標找出來,只要有了上面兩級的行政區對象,則又可以通過之前的loadCity和loadArea兩個方法設置選擇器數據了:

setArea() { // 省市區數據回顯
    let index = 0;
    let cityList = [], areaList = [], provinceIndex = 0, cityIndex = 0, areaIndex = 0;
    const areaCode = this.data.form.areaCode;
    if(areaCode) {
      // 省級信息回顯
      const province = areaCodeToMap(`${areaCode.substring(0, 2)}0000`, srcProvince);
      const provinceItem = province.map;
      provinceIndex = province.index;

      // 地市級信息回顯
      cityList = loadCity(provinceItem);
      const city = areaCodeToMap(`${areaCode.substring(0, 4)}00`, cityList);
      const cityItem = city.map;
      cityIndex = city.index;

      // 區縣級信息回顯
      areaList = loadArea(cityItem);
      const area = areaCodeToMap(areaCode, areaList);
      const areaItem = area.map;
      areaIndex = area.index;
      
      this.setData({
        blockArray: [srcProvince, cityList, areaList],
        regionArray: [srcProvince, cityList, areaList],
        blockIndex: [provinceIndex, cityIndex, areaIndex],
        regionIndex: [provinceIndex, cityIndex, areaIndex],
        cityList,
        areaList,
      })
      this.setData({
        areaText: `${this.data.regionArray[0][this.data.regionIndex[0]].value}${this.data.regionArray[1][this.data.regionIndex[1]].value}${this.data.regionArray[2][this.data.regionIndex[2]].value}`
      })
}

上真機效果圖:

image

其實這里還涉及到省市區選擇器和地圖選址組件的交互,比如選擇完地區以后,將經緯度設置成該地區的經緯度,進入地圖后定位到該地區,還有在地圖定位完位置后根據地圖返回的areaCode再調用一遍setArea方法直接回顯省市區等等,這些不是本文探討范圍,不再細說了。

文章寫完了,喜歡的給個贊吧!

查看原文

贊 7 收藏 5 評論 1

文心雕刺 關注了專欄 · 2020-12-25

前端開發那些事兒

前端知識:HTML、CSS、JS、React,nodejs、Chrome、數據結構與算法,計算機網絡等精華知識分享交流。

關注 6232

文心雕刺 關注了專欄 · 2020-12-25

K8S生態

Container, Docker, Go, Kubernetes, Python, Vim; 微信公眾號: MoeLove

關注 10588

文心雕刺 關注了專欄 · 2020-12-25

SegmentFault 之聲

在這里,我們將為你推送 SegmentFault 思否公司官方合作信息,和合作伙伴最新動態。SegmentFault 思否是中國領先的開發者社區和技術媒體,中國最大的 Hackathon 組織者。我們致力于成為科技企業和開發者溝通的橋梁,幫助科技企業和開發者對話。

關注 19044

文心雕刺 關注了用戶 · 2020-12-25

譚光志 @woai3c

公眾號:前端編程技術分享

知乎:https://www.zhihu.com/people/...

github: https://github.com/woai3c

關注 10265

文心雕刺 關注了用戶 · 2020-12-25

LNMPRG源碼研究 @php7internal

一群熱愛代碼的人 研究Nginx PHP Redis Memcache Beanstalk 等源碼 以及一群熱愛前端的人
希望交流的朋友請加微信 289007301 注明:思否 拉到交流群,也可關注公眾號:LNMPRG源碼研究

《PHP7底層設計與源碼分析》勘誤http://www.tvxinternet.com/a/11...

《Redis5命令設計與源碼分析》https://item.jd.com/12566383....

景羅 陳雷 李樂 黃桃 施洪寶 季偉濱 閆昌 李志 王坤 肖濤 譚淼 張仕華 方波 周生政 熊浩含 張晶晶(女) 李長林 朱棟 張晶晶(男) 陳朝飛 巨振聲 楊曉偉 閆小坤 韓鵬 夏達 周睿 李仲偉 張根紅 景羅 歐陽 孫偉 李德 twosee

關注 11662

文心雕刺 關注了用戶 · 2020-12-25

王治治 @wangdazhi_sifou

學者所志至大,猶恐所得淺。

關注 5187

文心雕刺 關注了用戶 · 2020-12-25

劉征Martin @liumartin

Elastic公司社區布道師,中國DevOps社區組織者,精通DevOps/SRE/ITIL等理論體系。致力于在全國范圍內通過社區推廣DevOps的理念、技術和實踐。熱衷于傳播開源技術棧在各種場景的應用,包括運維大數據分析、云原生服務治理、APM全鏈路監控和AIOps等。 微信號:MyDevOps

關注 2906

文心雕刺 關注了專欄 · 2020-12-25

concent

concent,一個內置依賴收集系統,并兼具0入侵、可預測、漸進式、高性能特點的react狀態管理方案。

關注 9301

認證與成就

  • 獲得 42 次點贊
  • 獲得 0 枚徽章 獲得 0 枚金徽章, 獲得 0 枚銀徽章, 獲得 0 枚銅徽章

擅長技能
編輯

(??? )
暫時沒有

開源項目 & 著作
編輯

(??? )
暫時沒有

注冊于 2018-10-10
個人主頁被 2.3k 人瀏覽

一本到在线是免费观看_亚洲2020天天堂在线观看_国产欧美亚洲精品第一页_最好看的2018中文字幕 <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>