betway必威-betway必威官方网站
做最好的网站

下拉刷新,最近实际项目中遇到的技术问题与解

  距上一篇博客发布已经过去整整2个月。这两个月中发生了一些事情,比如离职,面试,入职等等,感触颇多。其实一次好的面试,即使没有成功入职也会有很多收获。

  借用了两个久经考验的轮子:fastClick和better-scroll,介意可以就此打住。本文绝对原创,手打,思路清晰,知识不难,不适合大佬观看,谢谢。

Options 参数

  • startX: 0 开始的X轴位置
  • startY: 0 开始的Y轴位置
  • scrollY: true 滚动方向为 Y 轴
  • scrollX: 'true' 滚动方向为 X 轴
  • style="color: #888888">click: true 是否派发click事件
  • directionLockThreshold: 5
  • style="color: #888888">momentum: true 当快速滑动时是否开启滑动惯性
  • style="color: #888888">bounce: true 是否启用回弹动画效果
  • selectedIndex: 0 wheel 为 true 时有效,表示被选中的 wheel 索引
  • rotate: 25 wheel 为 true 时有效,表示被选中的 wheel 每一层的旋转角度
  • wheel: false 该属性是给 picker 组件使用的,普通的列表滚动不需要配置
  • snap: false 该属性是给 slider 组件使用的,普通的列表滚动不需要配置
  • style="color: #888888">snapLoop: false 是否可以无缝循环轮播
  • style="color: #888888">snapThreshold: 0.1 用手指滑动时页面可切换的阈值,大于这个阈值可以滑动的下一页
  • snapSpeed: 400, 轮播图切换的动画时间
  • swipeTime: 2500 swipe 持续时间
  • style="color: #888888">bounceTime: 700 弹力动画持续的毫秒数
  • adjustTime: 400 wheel 为 true 有用,调整停留位置的时间
  • swipeBounceTime: 1200 swipe 回弹 时间
  • style="color: #888888">deceleration: 0.001 滚动动量减速越大越快,建议不大于0.01
  • style="color: #888888">momentumLimitTime: 300 符合惯性拖动的最大时间
  • style="color: #888888">momentumLimitDistance: 15 符合惯性拖动的最小拖动距离
  • style="color: #888888">resizePolling: 60 重新调整窗口大小时,重新计算better-scroll的时间间隔
  • style="color: #888888">preventDefault: true 是否阻止默认事件
  • style="color: #888888">preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ } 阻止默认事件
  • style="color: #888888">HWCompositing: true 是否启用硬件加速
  • style="color: #888888">useTransition: true 是否使用CSS3的Transition属性
  • style="color: #888888">useTransform: true 是否使用CSS3的Transform属性
  • style="color: #888888">probeType: 1 滚动的时候会派发scroll事件,会截流。2滚动的时候实时派发scroll事件,不会截流。 3除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件

 

  这次面试面了三家公司,拿了两家公司的offer,但是最让我中意的面试却没拿到offer,原因是下午去面试,精神状况不太好,有点疲倦并且反应有点迟钝,导致整个面试很糟糕。不过面试官还是很nice的,给我梳理了一下头绪,拓展了一下思维,并且在后来的交流中给了我一些建议。准确的说这其实是一次成功的面试,长进很多。

  首先说一下,我不是阿里的人,也没去阿里面试过,这是某微信群里的一个小伙伴给的,我现在的能力达不到阿里的要求。不过人没梦想还不如咸鱼,有能力的话还是想去尝试一下。本文如有不足,请勿嘲讽,指出不足即可,谢谢。码字不易,且看且珍惜,转载请注明出处。原创博客,若侵犯贵司的利益,请私信我删除。若觉得不错,求个赞和github的star。

Events 事件

  • beforeScrollStart - 滚动开始之前触发
  • scrollStart - 滚动开始时触发
  • scroll - 滚动时触发
  • scrollCancel - 取消滚动时触发
  • scrollEnd - 滚动结束时触发
  • touchend - 手指移开屏幕时触发
  • flick - 触发了 fastclick 时的回调函数
  • refresh - 当 better-scroll 刷新时触发
  • destroy - 销毁 better-scroll 实例时触发

Example:

class="storage type js">let scroll  class="keyword operator assignment js">=  class="meta class instance constructor"> class="keyword operator js">new  class="entity name type instance js">BScroll class="meta brace round js">( class="support class js">document class="meta delimiter method period js">. class="support function dom js">getElementById class="meta js"> class="punctuation definition begin round js">( class="string quoted single js"> class="punctuation definition string begin js">'wrapper class="punctuation definition string end js">' class="punctuation definition end round js">) class="meta delimiter object comma js">, class="meta brace curly js">{

   probeType class="keyword operator js">:  class="constant numeric decimal js">3

class="meta brace curly js">} class="meta brace round js">)

 

class="variable other object js">scroll class="meta delimiter method period js">. class="entity name function js">on class="punctuation definition begin round js">( class="string quoted single js"> class="punctuation definition string begin js">'scroll class="punctuation definition string end js">' class="meta delimiter object comma js">,  class="meta function js"> class="punctuation definition begin round js">( class="variable function js">pos class="punctuation definition end round js">)  class="storage type function js">=>  class="punctuation definition function begin curly js">{

class="meta js">   class="entity name type object console js">console class="meta js">. class="support function console js">log class="punctuation definition begin round js">( class="variable other object js">pos class="meta delimiter period js">. class="support constant js">x  class="keyword operator js">   class="string quoted single js"> class="punctuation definition string begin js">'~ class="punctuation definition string end js">'  class="keyword operator js">   class="variable other object js">pos class="meta delimiter period js">. class="support constant js">y class="punctuation definition end round js">)

class="meta js"> class="punctuation definition function end curly js">} class="punctuation definition end round js">)

 

  最近一个月有点懒,说着努力进步的话做着混吃等死的事。因此给自己定了一个目标,每个月保证有20天晚上坚持学习,一周至少一篇博客,完成之后给自己一个小奖励,比如一双喜欢的AJ11之类的。

  题目如下:

方法列表

  • scrollTo(x, y, time, easing)

滚动到某个位置,x,y 代表坐标,time 表示动画时间,easing 表示缓动函数
scroll.scrollTo(0, 500)

 

  • scrollToElement(el, time, offsetX, offsetY, easing)

滚动到某个元素,el(必填)表示 dom 元素,time 表示动画时间,offsetX 和 offsetY 表示坐标偏移量,easing 表示缓动函数

 

  • refresh()

强制 scroll 重新计算,当 better-scroll 中的元素发生变化的时候调用此方法

 

  • getCurrentPage()

当 snap 为 true 时,获取滚动的当前页,返回的对象结构为 {x, y, pageX, pageY},其中 x,y 代表滚动横向和纵向的位置;pageX,pageY 表示横向和纵向的页面索引

 

  • goToPage(x, y, time, easing)

当 snap 为 true,滚动到对应的页面,x 表示横向页面索引,y 表示纵向页面索引, time 表示动画,easing 表示缓动函数

 

  • enable()

启用 better-scroll,默认开启

 

  • disable()

禁用 better-scroll

 

  • destroy()

销毁 better-scroll,解绑事件

 

  入职新公司20来天吧,跟进了一个项目,pc端的运营后台和移动端的混合开发app,算是现在项目组同时在做pc和移动端的唯一一个吧,有时候很懵逼,打开编辑器第一反应是看看这是哪一端的代码。在这两端的项目中帮同事改了几个bug。

图片 1

左右滑动的组件 - slider.vue:

  • 实现效果:

自动轮播, 左右滑动切换,循环轮播

  • 效果图:

图片 2

 

  • 代码:

slider.vue

<template>
  <div class="slider" ref="slider">
    <div class="slider-group" ref="sliderGroup">
      <slot></slot>
    </div>
    <div class="dots">

      <!--上图点的html  -->
      <span class="dot" :class="{active: currentPageIndex === index }" v-for="(item, index) in dots"></span>
    </div>
  </div>
</template>

 

<script type="text/ecmascript-6">

import {addClass} from 'common/js/dom'
import BScroll from 'better-scroll'

export default {

name: 'slider',
props: {
  loop: {  //是否循环播放
    type: Boolean,
    default: true
  },
  autoPlay: { //是否自动播放
    type: Boolean,
    default: true
  },
  interval: { //轮播频率
    type: Number,
    default: 4000
  }
},

data() {
  return {
    dots: [],          // 用于设置有多小个点
    currentPageIndex: 0 // 当前播放到第几张图片
  }
},
mounted() {

  // 加上延迟20毫秒是因为dom初始化完,才可以执行以下操作
  setTimeout(() => {
    this._setSliderWidth()   //初始化宽度
    this._initDots()            //初始化上图的点
    this._initSlider()          //初始化better-scroll 

    if (this.autoPlay) {
      this._play()      // 自动播放
    }
  }, 20)

  //resize 监听设备窗口发生变化,要重新计算高宽

  window.addEventListener('resize', () => {
    if (!this.slider) {
      return
    }
    this._setSliderWidth(true)
    this.slider.refresh()
  })
},

activated() {
  if (this.autoPlay) {
    this._play()   // 进入页面,重新执行自动播放,因为跳出页面会清除定时器
  }
},
deactivated() {
  clearTimeout(this.timer)  //跳出路由,清理定时器
},
beforeDestroy() {
  clearTimeout(this.timer)  //清理定时器
},
methods: {

  // 初始化高宽方法
  _setSliderWidth(isResize) {

    // <slot></slot>应该会被外面组件通过for循环生成的列表替换,获取这些列表
    this.children = this.$refs.sliderGroup.children

    // 用于保存总宽度
    let width = 0

    //获取组件的宽度
    let sliderWidth = this.$refs.slider.clientWidth
    for (let i = 0; i < this.children.length; i ) {
      let child = this.children[i]

      // 在这里添加样式,是因为如果在父组件添加样式,会增强父子组件的依赖
      addClass(child, 'slider-item')

      // 给每一个子组件的宽度,都应该=组件最外层div的宽度,保证每次轮播,都能填满
      child.style.width = sliderWidth 'px'
      width = sliderWidth

    }
    if (this.loop && !isResize) {

      //循环播放的时,在第一个元素之前,会自动添加最后一个元素的克隆对象,

      //在最后一个元素之后,也会自动添加第一个元素的克隆对象,

      // 所以比实际上市多出2和元素, 计算宽度要 style="color: #ff0000">加 2 * sliderWidth
      width = 2 * sliderWidth
    }
    this.$refs.sliderGroup.style.width = width 'px'
  },
  _initSlider() {

this.slider = new BScroll(this.$refs.slider, {
  scrollX: true,
  scrollY: false,
  momentum: false,
  snap: true,
  snapLoop: this.loop,
  snapThreshold: 0.3,
  snapSpeed: 400

    })

this.slider.on('scrollEnd', () => {

  // 获取当前播放的索引
  let pageIndex = this.slider.getCurrentPage().pageX
  if (this.loop) {

    // 如果是循环播放,实际当前播放元素的索引,计算要减去1,原因也是多了2个元素
    pageIndex -= 1
  }

  // 改变当前选中的样式
  this.currentPageIndex = pageIndex

  if (this.autoPlay) {
    clearTimeout(this.timer)

    // 能自动播放的关键,每次滑动完成都会重新调用 _play 方法
    this._play()
  }
})

  },
  _initDots() {
    this.dots = new Array(this.children.length)
  },
  _play() {
    let pageIndex = this.currentPageIndex 1
    if (this.loop) {
      pageIndex = 1
    }
    this.timer = setTimeout(() => {

      // 跳转到哪个页面
      this.slider.goToPage(pageIndex, 0, 400)
    }, this.interval)
  }
}

}

</script>

 

父组件引用插件:

import Slider from 'base/slider/slider'

components: {
  Slider
}

<!--  v-if="recommends.length"作用是保证有数据才会渲染组件  -->

<div v-if="recommends.length" class="slider-wrapper" ref="sliderWrapper">
  <slider>
    <div v-for="item in recommends">
      <a :href="item.linkUrl">

        <img class="needsclick" @load="loadImage" :src="item.picUrl">
      </a>
    </div>
  </slider>
</div>

 

方法loadImage :由于图片下载时异步的,所以有可能出现列表已经渲染好,但是图片还没有下载完的情况,导致的结果就是组件的宽度不正确,所以当图片加载完成,要重新计算宽度

loadImage() {
  if (!this.checkloaded) {
    this.checkloaded = true
    this.$refs.scroll.refresh()
  }
},

 

  1.改进自己写过的一个城市选择组件

  我以前写过一篇博客《独立完成一个城市选择组件(阿里前端题目,内附知识点、思路)》,并且将这个组件的代码分享在了github上,地址是。在这个项目中我引入了一个比较出色的插件better-scroll为我提供滚动事件,其中有一段代码是这样的:

// pos为位置参数
this.scroll.on('scroll', (pos) => {
   this.$emit('distance', Math.abs(pos.y))
   this.$emit('scrollStore', true)
})

  上面这段代码我的用意是触发滚动事件时应该向我返回滚动的位置,我在下面这段代码中用到了这个pos.y这个变量为我的组件在滚动时提供一个指示,比如点击右边的navList的A时,城市列表会滚动到A处,此时会有一个悬浮的卡片展示A。

    // 滚动到相应的dom节点
    singleLetter (dom) {
      this.$refs.suggest.scrollToElement(dom, 200, false, false)
    },
    // 根据滑动距离显示字母牌上的字
    distance (val) {
      for (let i = 0, len = this.arrHeight.length; i < len; i  ) {
        if (val < this.arrHeight[i]) {
          this.flagText = this.cityIndexList[i]
          return false
        }
      }
    }

  以上为以前的代码,scrollToElement是组件提供的事件。

  理想很丰满,现实很骨感。直到端午节放假下班回家时的一天,一个人加了我的微信,给我说了一个bug:一定几率下,从上往下点击时会出现点击了E,列表滚动到E但是悬浮卡片只显示D的情况存在,而从下往上点击时不会存在。后来也得到了证实的确几率性的存在。但是作为我第一个发到github上的作品,我希望它是完美的。因此我花了端午假期在研究问题的所在,直到前几天偶尔想到了原因。我们在触发一下类似与mousemove的事件时,理想中应该一个像素触发一次,但是因为机器的性能的原因并不会这样。 “触发”这个行为,从硬件发中断,然后OS,产生消息,这个过程中,鼠标硬件本身判断自己移动了多少距离时产生硬件信号,800dpi之类的。主板上中断芯片的处理速度也会影响。也许每个版本的系统 硬件本身,这个时间片长短都不定。而程序本身由于GetMessage函数本身消耗的时间,与捕获 鼠标移动消息(switch分支的判断) 所需要的时间也不太确定。当你鼠标移动的比较慢时,可能每一个像素触发一次。鼠标比较快时,可能发现移动了n个像素才触发一次。

  这也就导致了上面bug的产生,当从上往下点击navList时,可能滚动到E这个列表的前10个像素时触发了一次,而滚动到E列表时也就有可能因为时间太短未触发,此时位置像素值实在D的最后10个像素上,忠诚的逻辑代码也就会因为这10个像素的误差不给我返回E这个字母。那我们要做的就是在滚动完成时让列表继续产生一个滚动事件,继续滚动一个像素即可。

  幸运的是同样是上面那个滚动事件scrollToElement为我提供了一个参数

  scrollToElement(el, time, offsetX, offsetY, easing)

  • 参数:返回值:无
    • {DOM | String} el 滚动到的目标元素, 如果是字符串,则内部会尝试调用 querySelector 转换成 DOM 对象。
    • {Number} time 滚动动画执行的时长(单位 ms)
    • {Number | Boolean} offsetX 相对于目标元素的横轴偏移量,如果设置为 true,则滚到目标元素的中心位置
    • {Number | Boolean} offsetY 相对于目标元素的纵轴偏移量,如果设置为 true,则滚到目标元素的中心位置
    • {Object} easing 缓动函数,一般不建议修改,如果想修改,参考源码中的 ease.js 里的写法
    • 作用:滚动到指定的目标元素。

  我们可以将上面的函数改为:

    // 滚动到相应的dom节点
    singleLetter (dom) {
      this.$refs.suggest.scrollToElement(dom, 200, false, 1)
    } 

  滚动到相关dom节点后继续向下滚动一个像素,再次触发一次滚动事件,解决。

  相关代码已更新,请放心使用,,求个star。

  大概就是这样吧,分析一下就是做一个城市选择组件,实现的功能或者要求呢就是可以定位当前的城市、用localstorage存储上次定位的城市和最近选择过的城市、可以按照输入的字母或者文字筛选出想找的城市、将数据带到页面也就是一个父子传参的问题吧、页面使用flex布局。

  2.vue2.0s中eventBus的绑定与解绑

  在移动端项目中,有两个页面共用了一个我封装的列表组件,并且这两个页面都会有一个上拉获取更多的功能,因此,我做这个组件时使用eventBus作为兄弟组件传值的转接站。当页面滚动至底部并且触发上拉事件时向列表组件传递一个事件,在列表页面绑定一个获取更多的事件,并触发。代码如下:

//滚动组件
pullup(event) {  
   Bus.$emit('getMore');   
} 

//列表组件
created() {  
   Bus.$on('getMore', this.getMoreList);  
},
methods: {  
   getMoreList() { 
    //
    // 
    //
   }  
}   

  很不幸的是,这两个列表组件我共用了一个组件,导致了下面这个问题:当这两个列表切换几次之后,上拉刷新时会触发多次getMoreList事件,并且切换多少次就会出发多少次。百思不得骑姐之后我想到Bus.$on('getMore', this.getMoreList)比较类似原生Js的事件监听:

//伪代码
相关列表组件.addEventListener("getMore",getMoreList);
function getMoreList(){
 alert("hello world!");
}

  在上述的问题中,假如事件getMore与组件中的getMoreList绑定,即使组件销毁了,但是这个绑定关系还是存在的。等再次渲染组件时,created生命周期又会绑定一次事件,并且以前的绑定关系还是存在的,现在组件中有两个绑定关系,而且相同。因此,在组件销毁时,我们应清除组件中的这个绑定关系:

destroyed() {  
   Bus.$off('getMore', this.getMoreList);  
}

  我在下班闲暇时间简单的做了一下,成功如下:

  3.路由前进后退时的切换动态

  打开手机app页面,当页面前进时,切换效果一般为从右向左滑动。当后退时,我们会希望他是从左向右退出的,但是vue提供的过渡效果只允许我们有一种的效果的存在,除非根据路由的切换来改变过渡效果绑定的name值。在实现这个效果的时候,我对原有的方法进行改进。我首先想到改写router.back()这个事件,但是因为觉得这样不太好,因此对这个进行了一层封装:

//router文件
Router.prototype.goBack = function () {
  store.commit("changeIsBack",true)
  this.back(-1)
}
//vuex文件
const state = {
  isBack:false
}
const mutations = {
  changeIsBack(state, flag) {
    state.isBack = flag
  }
}
export default {
  state,
  mutations
}

  这个封装中的this.back(-1)就是this.$router.back(-1)。在router中引入vuex的store文件,使用commit改变state的值。

//pageMain.vue文件
methods: {
    ...mapMutations([
      'changeIsBack'
    ])
}
beforeRouteUpdate (to, from, next) {
    let isBack = this.$store.state.routerState.isBack
    if (isBack) {
      this.transitionName = 'enter-right'
    } else {
      this.transitionName = 'enter-left'
    }
    this.changeIsBack(false)
    next()
}
//其余单页返回上一级
this.$router.goback()

  在页面展示页使用beforeRouteUpdate这个钩子函数检测store中isBack的值。当页面返回时调用this.$router.goback()这个方法,此时会改变store中isBack的值。当路由更新时,检测isBack是否为true,如果是则为返回的页面,此时更新过渡transition的name,达到更新的目的。方案地址:。

 

  以上就是最近遇到的问题与改进解决思路。

  当初因为觉得很酷炫选择了这条路,就要好好地走下去,晚安。

  掘金地址:。

 

 图片 3

图片 4

  图片 5

  我仅仅做了这个组件,向页面传参的功能还没做,可以用父子组件传参完成。

知识点部分:

  简单的说一下我这个城市选择组件和其中的一下知识点:

  1.后台

    我用node.js起了一个后台服务,使用的express框架,完成满足了我的需求。我的数据来源是爬取的某网站的城市地址(若侵权请联系我删除),数据是这样的:

    {
      "id": 151,
      "name": "鞍山",
      "pinyin": "anshan",
      "acronym": "as",
      "rank": "C",
      "firstChar": "A"
    }

    我在node端调用了某浪的一个定位接口作为我的定位服务,并将数据返回,当这个接口有问题或者没获取到的时候会返回定位在北京。具体代码为:

// 获取城市数据,city为我爬取的信息
app.get('/', function (req, res) {
    res.send(city);
    res.end()
});
// 调用新浪的接口返回定位
app.get('/nowcity', function (req, res) {
    let getIpInfo = function (cb) {
        var url = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json';
        http.get(url, function (res) {
            var code = res.statusCode;
            if (code == 200) {
                res.on('data', function (data) {
                    try {
                        cb(JSON.parse(data));
                    } catch (err) {
                        console.log(err)
                    }
                });
            }
        }).on('error',function(e){
            cb({
                city: "北京",
                country: "中国",
                province: "北京",
            })
        })
    };
    getIpInfo(function (msg) {
        let nowcity = msg
        res.send(nowcity)
        res.end()
    })
});

  2.vue脚手架

    本次组件基于vue框架,我使用vue-cli脚手架搭建的,这一块知识不多做描述,参考我的博客《vue环境搭建与创建第一个vuejs文件》。

本文由betway必威发布于网页设计,转载请注明出处:下拉刷新,最近实际项目中遇到的技术问题与解

Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。