IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue弹性标题栏(收缩扩张标题栏背景) -> 正文阅读

[JavaScript知识库]Vue弹性标题栏(收缩扩张标题栏背景)

前言

最近在做专科毕设个人资料部分,发现酷安的效果不错,遂开始借鉴。

酷安效果

Video_20210926_015832_562.gif

我的效果

Video_20210926_015909_456.gif

我的思路是把页面分成两块:
1.顶部标题栏区
2.下方主内容区。

顶部标题区分成普通状态,收缩状态,扩张状态
触摸屏幕的时候先判断顶部当前处于哪种状态。只有处于收缩状态时才开启主内容区域滚动。其他状态下主内容是无法滚动的,通过改变头部区域高度挤压下方内容实现下方伪滚动效果。

<template>
  <div id="root">
    <div id="header" :style="'height:'+navNormalHeight+'px'">
      <div>你好</div>
    </div>
    <div id="content" :style="'height:calc(100% - '+navShrinkHeight+'px)'">
      <div v-for="i in 100">{{i}}</div>
    </div>
  </div>
</template>

先关闭页面整体滚动

document.documentElement.style.overflowY = 'hidden'

监听触摸事件,判断滑动方向。

这里提一句,判断滑动方向网上不少帖子是通过在touchstart手指刚接触屏幕时设个变量,然后滑动时判断手指当前位置是否大于或小于初始触摸位置实现的,这显然是偷懒的做法。刚开始我也这样做,后面被坑了一把:比如初始触摸在y=600的位置,此时向上划到y=400,判断上滑没错,但是不松手下滑到y=500的位置方向判断就出问题了,因为还是在600上方,所以还是判断是上滑动作

设置顶部三个状态的高度

      navNormalHeight: 300,//默认300
      navShrinkHeight: 65,//缩小状态65
      navExpandHeight: 500//扩张状态500

获取顶部和主内容节点

    let header = document.getElementById('header')
    let content = document.getElementById('content')

设一个变量记录上次手指所在位置
let oldY
设一个变量标识方向
let up
监听touchmove事件,获取第一根手指的触摸对象(这里就不考虑多指触摸了)
let touches = ev.touches[0]
手指移动一像素点时给oldY赋值,直接return不进行其他操作。
在移动监听的最后位置再给oldY赋值为当前坐标。

window.addEventListener('touchmove', ev => {
      let touches = ev.touches[0]
      //第一次移动仅获取坐标,不进行其他操作
      if (oldY===undefined){
        oldY=touches.clientY
        return
      }
      //比较新旧坐标获得准确方向
      up = touches.clientY <= oldY
     /*
      主要逻辑
      */  
      //最后记录旧坐标
      oldY = touches.clientY
    })

处理上划逻辑

1.顶部未完成收缩
先关掉主内容滚动

if (content.style.overflowY === 'scroll') {
    content.style.overflowY = 'hidden'
  }

计算滑动偏移量
监听touchstart事件,记录刚开始触摸的位置。

 window.addEventListener('touchstart', ev => {
      let touches = ev.touches[0];
      //记录初始位置
      startY = touches.clientY
    })

上滑偏移量=触摸初始位置-手指当前位置

let offY = startY - touches.clientY

顶部区域高度=顶部默认高度-偏移量

if (header.clientHeight > _this.navShrinkHeight) {
    if (content.style.overflowY === 'scroll') {
        content.style.overflowY = 'hidden'
      }
      let number = _this.navNormalHeight - offY 
      header.style.height = number + 'px'
  }

现在已经可以随着手指的上滑,实现顶部区域的收缩了,同时下方区域也在向上填充。
Video_20210926_032021_293.gif

但是如果手指离开后再次触摸接着上次的位置滑动,会发现每次都是从默认的高度开始向上缩小,显然不合理。所以我们再定义一个变量,记录手指离开时的顶部区域高度

监听touchend事件

//初始值为默认高度
let lastHeight=this.navNormalHeight
window.addEventListener('touchend', ev => {
      lastHeight = header.clientHeigh
    })

手指离开后记录顶部高度,下次动态设置高度的时候减去默认高度与上次的高度的偏差即可

 let number = _this.navNormalHeight - offY - (_this.navNormalHeight - lastHeight);
  header.style.height = number + 'px'

此时手指离开后重新上滑的高度问题解决。
又发现一个问题:
我们修改顶部高度的条件是顶部高度大于缩小状态高度,到缩小状态也停止缩小了,但是高度不对啊,设好的65不灵了
image.png
我猜测是滑动延时,执行延时导致的偏差(移动端点击事件延迟300毫秒)。只需要在修改高度的语句下调用下修正函数即可。

      function formatY(){
        //避免过度缩小
        if (_this.navHeight <= _this.navShrinkHeight) {
          _this.navHeight = _this.navShrinkHeight
        }
        //避免过度扩张
        if (_this.navHeight >= _this.navExpandHeight) {
          _this.navHeight = _this.navExpandHeight
        }
      }

修改完高度如果有偏差就再调整到设计的范围内即可。
至此顶部普通状态到收缩状态的过程写完了。
2.顶部完成收缩
此时就不用对顶部高度进行修改了。
打开下方主内容滑动

        content.style.overflowY = 'scroll'

此时下方可以滑动,但是从未收缩状态到收缩状态到主内容打开滑动过程中一直不松手的话主内容动不了,必须抬手重新向上划才可以滑动主内容。缺少连贯性,所以我们手动给主内容设置scrollTop滚动起来。

let offO = offY - (lastHeight - _this.navShrinkHeight)
content.scrollTop = offO

此时有了连贯性,解决下再次触摸滚动位置不对的问题:
对主内容滚动进行监听,停止后记录位置,
由于js没有对滚动结束的监听,只能通过函数防抖,延时200毫秒获取最后一次滚动时主内容的scrollTop

let timeOut
    content.addEventListener('scroll', ev => {
      if (timeOut !== undefined) {
        clearTimeout(timeOut)
      }
      timeOut = setTimeout(() => {
        lastContentY = content.scrollTop
      }, 200)
    })

修改

 let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
 content.scrollTop = offO

上滑处理完成,下滑举一反三:

//主内容顶部展现
        if (content.scrollTop === 0) {
          if (content.style.overflowY === 'scroll') {
            content.style.overflowY = 'hidden'
            startY = touches.clientY;
            lastHeight=_this.navShrinkHeight
          }
          let offY = touches.clientY - startY
          let newHeight = (lastHeight + offY)
          header.style.height = newHeight + 'px'
          formatHeight()
        }
        //主内容顶部未出现
        else {
          let offY = startY - touches.clientY
          if (content.style.overflowY === 'hidden') {
            content.style.overflowY = 'scroll'
          }
          let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
          content.scrollTop = offO
        }

上下滑动已经完成了,处理一下松手回弹
这里我们用间歇函数每5毫秒减去顶部5高度。

window.addEventListener('touchend', ev => {
      lastHeight = header.clientHeight
      //高度大于普通高度,触发回弹动画
      if (lastHeight>=_this.navNormalHeight){
        let interval=setInterval(()=>{
          if (header.clientHeight<=_this.navNormalHeight+5){
            clearInterval(interval)
          }
          let height=header.clientHeight-5
          header.style.height=height+'px'
          if (header.clientHeight<=_this.navNormalHeight+5){
            lastHeight=_this.navNormalHeight
          }
        },5)
      }
    })

添加阴影遮罩

 <div class="header" :style="'height:'+navNormalHeight+'px;'" id="header">
      <div id="headerRoot">
        <img :src="coverImg" class="coverImg">
        <slot name="headerInfo"></slot>
        <div id="mask"></div>
      </div>
    </div>
#mask{
  z-index: 8;
  height: 100%;
  width: 100%;
  position: absolute;
  pointer-events: none;
}

动态设置透明度

if (header.clientHeight<=_this.navNormalHeight){
        let temp = _this.navNormalHeight - header.clientHeight
        let val = temp / _this.navNormalHeight
        mask.style.background = 'rgba(0, 0, 0, '+val+')'
  }

完整代码

<template>
  <div class="root">
    <div class="header" :style="'height:'+navNormalHeight+'px;'" id="header">
      <div id="headerRoot">
        <img :src="coverImg" class="coverImg">
        <div id="imgMask"></div>
        <slot name="headerInfo"></slot>
        <div id="mask"></div>
      </div>
    </div>
    <div class="content" id="content">
      <slot name="content"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "Temp",
  props: {
    navNormalHeight: 0,
    navShrinkHeight: 0,
    navExpandHeight: 0,
    coverImg: ''
  },
  mounted() {
    let _this = this;
    let header = document.getElementById('header')
    let content = document.getElementById('content')
    let mask = document.getElementById('mask')

    document.documentElement.style.overflowY = 'hidden'

    let startY, lastContentY = 0
    let oldY
    let up, lastHeight = _this.navNormalHeight
    let beTouch=false

    function start(ev) {
      let touches = ev.touches[0];
      startY = touches.clientY
      beTouch=true
    }


    function move(ev) {
      let touches = ev.touches[0]
      //第一次移动仅获取坐标,不进行其他操作
      if (oldY === undefined) {
        oldY = touches.clientY
        return
      }
      up = touches.clientY <= oldY
      // console.log(up);
      //上滑
      if (up) {
        let offY = startY - touches.clientY
        if (header.clientHeight > _this.navShrinkHeight) {
          if (content.style.overflowY === 'scroll') {
            content.style.overflowY = 'hidden'
          }
          let number = _this.navNormalHeight - offY - (_this.navNormalHeight - lastHeight);
          header.style.height = number + 'px'
          formatHeight()
        }
        //顶部收缩完成
        else {
          // console.log(content.style.overflowY)
          //打开主内容滑动(下方滑动)
          content.style.overflowY = 'scroll'

          let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
          content.scrollTop = offO
        }
      }
      //下滑
      else {
        //主内容顶部展现
        if (content.scrollTop === 0) {
          if (content.style.overflowY === 'scroll') {
            content.style.overflowY = 'hidden'
            startY = touches.clientY;
            lastHeight = _this.navShrinkHeight
          }
          let offY = touches.clientY - startY
          let newHeight = (lastHeight + offY)
          header.style.height = newHeight + 'px'
          formatHeight()
        }
        //主内容顶部未出现
        else {
          let offY = startY - touches.clientY
          if (content.style.overflowY === 'hidden') {
            content.style.overflowY = 'scroll'
          }
          let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
          content.scrollTop = offO
        }
      }
      if (header.clientHeight <= _this.navNormalHeight) {
        let temp = _this.navNormalHeight - header.clientHeight
        let val = (temp / _this.navNormalHeight) - 0.2
        mask.style.background = 'rgba(0, 0, 0, ' + val + ')'
      }
      if (header.clientHeight <= _this.navExpandHeight) {
        _this.$emit('navHide', header.clientHeight)
      }
      //电脑端模拟手机滑动到屏幕外修正
      if (touches.clientY < document.documentElement.clientHeight
          && touches.clientX < 0 && touches.clientX > document.documentElement.clientWidth) {
        oldY = touches.clientY
      }
      oldY = touches.clientY
    }


    function end(ev) {
      beTouch=false
      lastHeight = header.clientHeight
      //高度大于普通高度,触发回弹动画
      if (lastHeight >= _this.navNormalHeight) {
        let interval = setInterval(() => {
          if (header.clientHeight <= _this.navNormalHeight + 5) {
            clearInterval(interval)
          }
          let height = header.clientHeight - 5
          header.style.height = height + 'px'
          if (header.clientHeight <= _this.navNormalHeight + 5) {
            lastHeight = _this.navNormalHeight
          }
        }, 5)
      }
    }


    //函数防抖
    let timeOut
    function beScroll(ev) {
      if (timeOut !== undefined) {
        clearTimeout(timeOut)
      }
      if (beTouch){
        return
      }
      timeOut = setTimeout(() => {
        lastContentY = content.scrollTop
      }, 1)
    }


    function formatHeight() {
      if (header.clientHeight < _this.navShrinkHeight) {
        header.style.height = _this.navShrinkHeight + 'px'
      }
      if (header.clientHeight >= _this.navExpandHeight) {
        header.style.height = _this.navExpandHeight + 'px'
      }
    }

    window.addEventListener('touchstart', start)
    window.addEventListener('touchmove', move)
    window.addEventListener('touchend', end)
    content.addEventListener('scroll', beScroll)

    this.$on('hook:beforeDestroy', () => {
      console.log('销毁事件')
      window.removeEventListener('touchstart',start)
      window.removeEventListener('touchmove',move)
      window.removeEventListener('touchend',end)
      content.removeEventListener('scroll',beScroll)
      document.documentElement.style.overflowY = 'auto'
    })
  }
}
</script>

<style lang="less" scoped>
.coverImg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
}

.root {
  height: 100%;
  width: 100%;
  position: fixed;
}

.header {
  width: 100%;
  overflow: hidden;
}

.content {
  height: calc(100% - 65px);
  width: 100%;
  background-color: @bg-color;
  overflow-y: hidden;
}

#mask {
  z-index: 8;
  height: 100%;
  width: 100%;
  position: absolute;
  pointer-events: none;
}

#imgMask {
  height: 100%;
  width: 100%;
  position: absolute;
  background-color: rgba(0, 0, 0, 0.45);
  pointer-events: none;
}

#headerRoot {
  position: relative;
  height: 100%;
  width: 100%;
}
</style>

正常使用,效果还不错。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-01 16:46:55  更:2021-10-01 16:47:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 0:44:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码