vue实现列表滚动的过渡动画

 更新时间:2020-06-29 15:45:37   作者:佚名   我要评论(0)

本文实例为大家分享了Vue实现列表滚动过渡动画的具体代码,供大家参考,具体内容如下
效果图
失帧比较严重,在手机上效果更佳。

原理分析
这个滚动页面由两个部分布

本文实例为大家分享了Vue实现列表滚动过渡动画的具体代码,供大家参考,具体内容如下

效果图

失帧比较严重,在手机上效果更佳。

原理分析

这个滚动页面由两个部分布局(底部固定的Tab页面除外)。一个是顶部的banner轮播,一个是下面的列表。这里的重点是做列表的动画,banner轮播的网上资料很多,请自行查找。

这个动画最重要的是在滚动中实时计算startIndex和endIndex,动画比较简单,就是scale和opacity的变化。向下滚动时,startIndex变小;向上滚动时,endIndex变大时,新露脸的项做该动画。当滚动连起来,就是一个完整的动画了。

涉及的技术

使用better-scroll做滚动以及轮播图

使用create-keyframe-animation做动画控制

实现步骤

1、vue的template部分

注意:由于IOS渲染速度比较快, 必须把没有展现在首屏的页面上的item隐藏掉,即index比startIndex小、比endIndex大的item都应该隐藏,避免页面动画混乱。

<div class="area-wrapper" ref="areaWrapper">
  <div v-for="(item, index) in areaList" :key="index"
  @click="clickAreaItem(item.id)"
  :ref="'area-' + index" class="area"
  :style="{ backgroundImage: 'url('+item.thumbUrl+')', 'opacity': (index < startIndex || index > endIndex) ? 0 : 1}">
    <div class="content">
    <h2 class="num">{{item.num}}</h2>
    <div style="vertical-align:text-bottom">
      <p class="name">{{item.name}}</p>
      <p class="desc">{{item.desc}}</p>
    </div>
    </div>
  </div>
</div>

高度预设。用于计算startIndex、endIndex

const AreaItemHeight = 119 // 每一项的高度(这里默认一致,如果不一致请自行修改startIndex、endIndex的计算方式)
const MarginBottom = 15   // 列表项的底部边距
const TopHeight = 160    // banner的高度
const BottomHeight = 50   // 底部Tab的高度

监听滚动。并实时计算startIndex、endIndex

scroll (position) {
  const scrollY = position.y
  if (scrollY < 0) {
    // startIndex计算
    const currentStartIndex = Math.abs(scrollY) <= TopHeight ? 0 : parseInt((Math.abs(scrollY) - TopHeight) / (AreaItemHeight + MarginBottom))
    // endIndex计算
    let currentEndIndex = Math.floor((window.innerHeight - (TopHeight + scrollY) - BottomHeight) / (AreaItemHeight + MarginBottom))
    if (currentEndIndex > this.areaList.length - 1) {
      currentEndIndex = this.areaList.length - 1
    }
    // 这里使用vue的watch属性监听更好
    if (currentStartIndex !== this.startIndex) {
      if (currentStartIndex < this.startIndex) {
        // 运行动画
        this.runAnimation(currentStartIndex)
      }
      this.startIndex = currentStartIndex
    }
    // 这里使用vue的watch属性监听更好
    if (currentEndIndex !== this.endIndex) {
      if (currentEndIndex > this.endIndex) {
      this.runAnimation(currentEndIndex)
      }
      this.endIndex = currentEndIndex
    }
  }
}

运行动画

runAnimation (index) {
  animations.registerAnimation({
    name: 'scale',
    animation: [
      {
      scale: 0.5,
      opacity: 0
      },
      {
      scale: 1,
      opacity: 1
      }
    ],
    presets: {
      duration: 300,
      resetWhenDone: true
    }
  })
  animations.runAnimation(this.$refs['area-' + index], 'scale')
}

完整代码

.vue文件

<template>
<div class="address-wrapper" style="height: 100%;">
 <scroll ref="scroll" class="address-content" :data="areaList" @scroll="scroll" :listen-scroll="listenScroll" :probe-type="probeType" :bounce="false">
  <div>
   <div v-if="bannerList.length" style="position: relative;">
    <slider :list="bannerList">
     <div v-for="item in bannerList" :key="item.id" :style="{height: sliderHeight + 'px'}">
      <img class="needsclick" :src="item.thumbUrl" width="100%" height="100%" />
     </div>
    </slider>
    <div class="banner-bg"></div>
    <div class="banner-bg-1"></div>
   </div>

   <div class="area-wrapper" ref="areaWrapper">
    <div v-for="(item, index) in areaList" :key="index"
    @click="clickAreaItem(item.id)"
    :ref="'area-' + index" class="area"
    :style="{ backgroundImage: 'url('+item.thumbUrl+')', 'opacity': (index < startIndex || index > endIndex) ? 0 : 1}">
     <div class="content">
      <h2 class="num">{{item.num}}</h2>
      <div style="vertical-align:text-bottom">
       <p class="name">{{item.name}}</p>
       <p class="desc">{{item.desc}}</p>
      </div>
      <!-- <div></div> -->
     </div>
    </div>
   </div>
  </div>
 </scroll>
 <router-view />
</div>
</template>

<script>
import Slider from '@/components/slider/slider'
import Scroll from '@/components/scroll/scroll'
import { isIphoneX } from '@/assets/js/brower'
import animations from 'create-keyframe-animation'
import axios from '@/api/axiosApi'
import areaList from '@/assets/json/areaList.json'
import bannerList from '@/assets/json/bannerAddress.json'

// 每一个的Area的高度,都是一样的
const AreaItemHeight = 119
const MarginBottom = 15
const TopHeight = 160
const BottomHeight = 50

export default {
 data () {
  return {
   startIndex: 0,
   endIndex: 3,
   bannerList,
   areaList
  }
 },
 components: {
  Slider, Scroll
 },
 created () {
  this.probeType = 3
  this.listenScroll = true
  this.sliderHeight = 210 + 20
  if (isIphoneX()) {
   this.sliderHeight += 34
  }

  this._getBanner()
  this._getAddressList()
 },
 mounted () {
  this.endIndex = Math.floor((window.innerHeight - TopHeight - BottomHeight) / (AreaItemHeight + MarginBottom))
 },
 methods: {
  _getBanner () {
   axios.get(this, '/v1/banner/1', null, (data) => {
    data.forEach(item => {
     item.thumbUrl += '-banner'
    })
    this.bannerList = data
   }, null, false)
  },
  _getAddressList () {
   axios.get(this, '/v1/address/1', {
    pageSize: 30
   }, (data) => {
    // data.forEach(item => {
    //  item.thumbUrl += '-tiaomu'
    // })
    this.areaList = data
   }, null, false)
  },
  scroll (position) {
   const scrollY = position.y
   if (scrollY < 0) {
    const currentStartIndex = Math.abs(scrollY) <= TopHeight ? 0 : parseInt((Math.abs(scrollY) - TopHeight) / (AreaItemHeight + MarginBottom))
    let currentEndIndex = Math.floor((window.innerHeight - (TopHeight + scrollY) - BottomHeight) / (AreaItemHeight + MarginBottom))
    if (currentEndIndex > this.areaList.length - 1) {
     currentEndIndex = this.areaList.length - 1
    }

    if (currentStartIndex !== this.startIndex) {
     if (currentStartIndex < this.startIndex) {
      this.runAnimation(currentStartIndex)
     }
     this.startIndex = currentStartIndex
    }
    if (currentEndIndex !== this.endIndex) {
     if (currentEndIndex > this.endIndex) {
      this.runAnimation(currentEndIndex)
     }
     this.endIndex = currentEndIndex
    }
   }
  },
  runAnimation (index) {
   animations.registerAnimation({
    name: 'scale',
    animation: [
     {
      scale: 0.5,
      opacity: 0
     },
     {
      scale: 1,
      opacity: 1
     }
    ],
    presets: {
     duration: 300,
     resetWhenDone: true
    }
   })
   animations.runAnimation(this.$refs['area-' + index], 'scale')
  },
  clickAreaItem (id) {
   this.$router.push(`address/addressDetail/${id}`)
  }
 }
}
</script>

<style lang="stylus" scoped>
.address-wrapper {
 .address-content {
  height: 100%;
  overflow: hidden;

  .banner-bg {
   height: 50px;
   width: 100%;
   position: absolute;
   bottom: -1px;
   background:-moz-linear-gradient(top, rgba(249, 250, 252, 0.3), rgba(249, 250, 252, 1));/*火狐*/
   background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(249, 250, 252, 0.3)), to(rgba(249, 250, 252, 1))); /*谷歌*/
   background-image: -webkit-gradient(linear,left bottom,left top,color-start(0, rgba(249, 250, 252, 0.3)),color-stop(1, rgba(249, 250, 252, 1)));/* Safari & Chrome*/
  }

  .banner-bg-1 {
   height: 20px;
   width: 100%;
   position: absolute;
   bottom: 49px;
   background:-moz-linear-gradient(top, rgba(249, 250, 252, 0), rgba(249, 250, 252, 0.3));/*火狐*/
   background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(249, 250, 252, 0)), to(rgba(249, 250, 252, 0.3))); /*谷歌*/
   background-image: -webkit-gradient(linear,left bottom,left top,color-start(0, rgba(249, 250, 252, 0)),color-stop(1, rgba(249, 250, 252, 0.3)));/* Safari & Chrome*/
  }

  .area-wrapper {
   transform: translateY(-45px)
   padding: 0 15px;
   z-index: 1;

   .area {
    margin-bottom: 15px;
    height: 119px;
    width: 100%;
    border-radius: 10px;
    background-repeat: no-repeat;
    background-size: cover;
    box-shadow: 0 0 10px #a4a3a3;
    display: flex;
    align-items: flex-end;

    .content {
     color: #fff;
     display: flex;
     padding-right: 60px;
     padding-bottom: 15px;
     line-height: 1.2;

     .num {
      bottom: 35px;
      font-size: 48px;
      font-weight: 100;
      padding: 0 15px;
      display:table-cell;
      vertical-align:bottom;
     }

     .name {
      font-size: 21px;
      font-weight: 600;
      line-height: 1.7;
     }

     .desc {
      font-size: 14px;
     }
    }
   }
  }
 }
}
</style>

本地json文件,请自行修改图片路径

bannerAddress.json

[
 {
  "id": 1,
  "contentId": 111111,
  "type": 1,
  "thumbUrl": "./static/img/banner/banner_address_1.jpg"
 },
 {
  "id": 2,
  "contentId": 111111,
  "type": 1,
  "thumbUrl": "./static/img/banner/banner_address_2.jpg"
 },
 {
  "id": 3,
  "contentId": 111111,
  "type": 1,
  "thumbUrl": "./static/img/banner/banner_address_3.jpg"
 }
]

areaList.json

[
 {
  "id": "ba062c32fdf611e7ba2d00163e0c27f8",
  "name": "凯里",
  "desc": "这是凯里哟~",
  "num": 17,
  "thumbUrl": "./static/img/area/kaili.png"
 }, {
  "id": "ba5287a7fdf611e7ba2d00163e0c27f8",
  "name": "丹寨",
  "desc": "这是丹寨哟~",
  "num": 8,
  "thumbUrl": "./static/img/area/danzai.png"
 }, {
  "id": "ba9da079fdf611e7ba2d00163e0c27f8",
  "name": "麻江",
  "desc": "这是麻江哟~",
  "num": 12,
  "thumbUrl": "./static/img/area/majiang.png"
 }, {
  "id": "baeb0926fdf611e7ba2d00163e0c27f8",
  "name": "黄平",
  "desc": "这是黄平哟~",
  "num": 7,
  "thumbUrl": "./static/img/area/huangping.png"
 }, {
  "id": "bb357191fdf611e7ba2d00163e0c27f8",
  "name": "施秉",
  "desc": "这是施秉哟~",
  "num": 6,
  "thumbUrl": "./static/img/area/shibing.png"
 }, {
  "id": "bb842d8ffdf611e7ba2d00163e0c27f8",
  "name": "镇远",
  "desc": "这是镇远哟~",
  "num": 3,
  "thumbUrl": "./static/img/area/zhenyuan.png"
 }, {
  "id": "bbce67dffdf611e7ba2d00163e0c27f8",
  "name": "岑巩",
  "desc": "这是岑巩哟~",
  "num": 23,
  "thumbUrl": "./static/img/area/cengong.png"
 }, {
  "id": "bc198ca9fdf611e7ba2d00163e0c27f8",
  "name": "三穗",
  "desc": "这是三穗哟~",
  "num": 66,
  "thumbUrl": "./static/img/area/sansui.png"
 }, {
  "id": "bc64498bfdf611e7ba2d00163e0c27f8",
  "name": "天柱",
  "desc": "这是天柱哟~",
  "num": 128,
  "thumbUrl": "./static/img/area/tianzhu.png"
 }, {
  "id": "bcaf466bfdf611e7ba2d00163e0c27f8",
  "name": "锦屏",
  "desc": "这是锦屏哟~",
  "num": 107,
  "thumbUrl": "./static/img/area/jinping.png"
 }, {
  "id": "bcfa6f1bfdf611e7ba2d00163e0c27f8",
  "name": "黎平",
  "desc": "这是黎平哟~",
  "num": 211,
  "thumbUrl": "./static/img/area/liping.png"
 }, {
  "id": "bd44cca9fdf611e7ba2d00163e0c27f8",
  "name": "从江",
  "desc": "这是从江哟~",
  "num": 17,
  "thumbUrl": "./static/img/area/congjiang.png"
 }, {
  "id": "bd8f5cd4fdf611e7ba2d00163e0c27f8",
  "name": "榕江",
  "desc": "这是榕江哟~",
  "num": 17,
  "thumbUrl": "./static/img/area/rongjiang.png"
 }, {
  "id": "bdda2928fdf611e7ba2d00163e0c27f8",
  "name": "雷山",
  "desc": "这是雷山哟~",
  "num": 17,
  "thumbUrl": "./static/img/area/leishan.png"
 }, {
  "id": "be25afc0fdf611e7ba2d00163e0c27f8",
  "name": "台江",
  "desc": "这是台江哟~",
  "num": 17,
  "thumbUrl": "./static/img/area/taijiang.png"
 }, {
  "id": "be702db5fdf611e7ba2d00163e0c27f8",
  "name": "剑河",
  "desc": "这是剑河哟~",
  "num": 17,
  "thumbUrl": "./static/img/area/jianhe.png"
 }
]

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:

  • Vue列表如何实现滚动到指定位置样式改变效果
  • Vue.js 无限滚动列表性能优化方案
  • vue实现歌手列表字母排序下拉滚动条侧栏排序实时更新
  • vue通过滚动行为实现从列表到详情,返回列表原位置的方法
  • Vue2.0 实现歌手列表滚动及右侧快速入口功能
  • Vuejs第七篇之Vuejs过渡动画案例全面解析
  • Vue.js每天必学之过渡与动画
  • Vue 过渡实现轮播图效果
  • 关于vue状态过渡transition不起作用的原因解决
  • Vue.js实现微信过渡动画左右切换效果

相关文章

  • vue实现列表滚动的过渡动画

    vue实现列表滚动的过渡动画

    本文实例为大家分享了Vue实现列表滚动过渡动画的具体代码,供大家参考,具体内容如下 效果图 失帧比较严重,在手机上效果更佳。 原理分析 这个滚动页面由两个部分布
    2020-06-29
  • python中如何写类

    python中如何写类

    类的定义 Python中,定义类是通过class关键字,例如我们定义一个存储学生信息的类: class Student(object): pass class后面紧接着是类名,即Student,类名通常是
    2020-06-29
  • 解决tensorflow/keras时出现数组维度不匹配问题

    解决tensorflow/keras时出现数组维度不匹配问题

    一、环境 Ubuntu 16.04 tensorflow 1.4.0 keras 2.1.3 二、训练数据时报错: ValueError: Error when checking target: expected model_2 to have shape (None, 3)
    2020-06-29
  • 微信小程序实现导航栏和内容上下联动功能代码

    微信小程序实现导航栏和内容上下联动功能代码

    &#8195;&#8195;今日给大家分享一下如何实现导航栏(nav)和内容部分上下联动(相关代码模块我已单独整理放到github上面了,欢迎前来start)。 github地址:https://
    2020-06-29
  • 解决keras使用cov1D函数的输入问题

    解决keras使用cov1D函数的输入问题

    解决了以下错误: 1.ValueError: Input 0 is incompatible with layer conv1d_1: expected ndim=3, found ndim=4 2.ValueError: Error when checking target: expec
    2020-06-29
  • python打开文件的方式有哪些

    python打开文件的方式有哪些

    python下打开文件超级简单,不用导入任何包,直接输入 f = open('your_file.txt','r') 就可以打开一个文件进行操作。第二个参数为对文件的操作方式,'w'是写文件
    2020-06-29
  • python怎么自定义捕获错误

    python怎么自定义捕获错误

    异常捕捉: try: XXXXX1 raise Exception(“xxxxx2”) except (Exception1,Exception2,……): xxxx3 else: xxxxx4 finally: xxxxxxx5 1.raise
    2020-06-29
  • vue实现页面切换滑动效果

    vue实现页面切换滑动效果

    本文实例为大家分享了vue实现页面切换滑动的具体代码,供大家参考,具体内容如下 试着用Vue做了个页面切换时滑动的效果,如下示例,源码 这里使用了Vue的transitio
    2020-06-29
  • python字典的值可以修改吗

    python字典的值可以修改吗

    python中字典的值是可以被修改的,首先我们得知道什么是修改字典 修改字典 向字典添加新内容的方法是增加新的键/值对,修改或删除已有键/值对如下实例: # !/usr/b
    2020-06-29
  • Pthon接口测试环境搭建过程详解

    Pthon接口测试环境搭建过程详解

    环境搭建 python 安装:建议使用python3.7 pycharm安装 requests安装 :pip3 install requests requests 基本使用 usage: >>> import requests
    2020-06-29

最新评论