建站教程

建站教程

Products

当前位置:首页 > 建站教程 >

Nuxt/Vue丝滑卡片式滑动|Vue仿探探卡片效果(vue使用vant下拉框van-dropdown-item 绑定title值不变问题)

GG网络技术分享 2025-03-18 16:14 0


Nuxt/Vue丝滑卡片式滑动|Vue仿探探卡片效果

最近在开发Nuxt项目,有个需求是实现类似探探左右滑动切换卡片功能。经过反复调试,最终实现了这个效果。

页面整体布局分为 顶部导航条、卡片滑动区、底部Tabbar 三个模块。

为了规范页面代码,其中卡片滑动区域单独抽离了一个组件flipcard.vue。

下面就讲解下卡片滑动页面实现方法。

  • 整体模板

<!-- //翻一翻模板 -->

<template>

<div>

<!-- >>顶部 -->

<header-bar :back="false" bgcolor="linear-gradient(to right, #00e0a1, #00a1ff)" fixed>

<div slot="title"><i class="iconfont icon-like c-red"></i> <em class="ff-gg">遇见TA</em></div>

<div slot="right" class="ml-30" @click="showFilter = true"><i class="iconfont icon-filter"></i></div>

</header-bar>

<!-- >>主页面 -->

<div class="nuxt__scrollview scrolling flex1" ref="scrollview" style="background: linear-gradient(to right, #00e0a1, #00a1ff);">

<div class="nt__flipcard">

<div class="nt__stack-wrapper">

<flipcard ref="stack" :pages="stackList" @click="handleStackClicked"></flipcard>

</div>

<div class="nt__stack-control flexbox">

<button class="btn-ctrl prev" @click="handleStackPrev"><i class="iconfont icon-unlike "></i></button>

<button class="btn-ctrl next" @click="handleStackNext"><i class="iconfont icon-like "></i></button>

</div>

</div>

</div>

<!-- >>底部tabbar -->

<tab-bar bgcolor="linear-gradient(to right, #00e0a1, #00a1ff)" color="#fff" />

</div>

</template>

项目中顶部导航栏/底部Tabbar组件,这里不作过多介绍,之前有一篇分享文章,感兴趣的可以看下。

基于Nuxt/Vue自定义Topbar+Tabbar

  • 侧边栏弹出框

侧边弹出层使用的是VPopup组件实现。其中范围滑块、switch、star等组件使用的是Vant组件库。

<!-- //侧边栏弹窗模板 -->

<v-popup v-model="showFilter" position="left" xclose xposition="left" title="高级筛选与设置">

<div class="flipcard-filter">

<div class="item nuxt-cell">

<label class="lbl">范围</label>

<div class="flex1">

<van-slider v-model="distanceRange" bar-height="2px" button-size="12px" active-color="#00e0a1" />

</div>

<em class="val">{{distanceVal}}</em>

</div>

<div class="item nuxt-cell">

<label class="lbl flex1">自动增加范围</label>

<em class="val"><van-switch v-model="autoExpand" size="20px" active-color="#00e0a1" /></em>

</div>

<div class="item nuxt-cell">

<label class="lbl flex1">性别</label>

<em class="val">女生</em>

</div>

<div class="item nuxt-cell">

<label class="lbl">好评度</label>

<div class="flex1"><van-rate v-model="starVal" color="#00e0a1" icon="like" void-icon="like-o" @change="handleStar" /></div>

<em class="val">{{starVal}}星</em>

</div>

<div class="item nuxt-cell">

<label class="lbl flex1">优先在线用户</label>

<em class="val"><van-switch v-model="firstOnline" size="20px" active-color="#00e0a1" /></em>

</div>

<div class="item nuxt-cell">

<label class="lbl flex1">优先新用户</label>

<em class="val"><van-switch v-model="firstNewUser" size="20px" active-color="#00e0a1" /></em>

</div>

<div class="item nuxt-cell mt-20">

<div class="mt-30 nuxt__btn nuxt__btn-primary--gradient" style="height:38px;"><i class="iconfont icon-filter"></i> 更新</div>

</div>

</div>

</v-popup>

  • 仿探探卡片滑块

滑动卡片封装成了一个flipcard.vue组件。只需传入pages数据即可。支持左右拖拽滑动、滑动回弹动画、点击按钮切换等功能。

<flipcard ref="stack" :pages="stackList" @click="handleStackClicked"></flipcard>

pages支持传入的JSON数据格式。

{

avatar: 'assets/img/avatar.jpg',

name: '甜甜圈',

sex: 'female',

age: 21,

starsign: '双鱼座',

distance: '800米',

// 照片集

photos: [...],

// 签名

sign: 'life is like a play 人生如戏。'

},

新建flipcard.vue页面

<!-- //卡片滑动模板 -->

<template>

<ul class="stack">

<li class="stack-item" v-for="(item, index) in pages" :key="index" :style="[transformIndex(index),transform(index)]"

@touchmove.stop.capture="touchmove"

@touchstart.stop.capture="touchstart"

@touchend.stop.capture="touchend($event, index)"

@touchcancel.stop.capture="touchend($event, index)"

@mousedown.stop.capture.prevent="touchstart"

@mouseup.stop.capture.prevent="touchend($event, index)"

@mousemove.stop.capture.prevent="touchmove"

@mouseout.stop.capture.prevent="touchend($event, index)"

@webkit-transition-end="onTransitionEnd(index)"

@transitionend="onTransitionEnd(index)"

>

<img :src="item.avatar" />

<div class="stack-info">

<h2 class="name">{{item.name}}</h2>

<p class="tags">

<span class="sex" :class="item.sex"><i class="iconfont" :class="'icon-'+item.sex"></i> {{item.age}}</span>

<span class="xz">{{item.starsign}}</span>

</p>

<p class="distance">{{item.distance}}</p>

</div>

</li>

</ul>

</template>

<script>

export default {

props: {

pages: {

type: Array,

default: {}

}

},

data () {

return {

basicdata: {

start: {},

end: {}

},

temporaryData: {

isStackClick: true,

offsetY: '',

poswidth: 0,

posheight: 0,

lastPosWidth: '',

lastPosHeight: '',

lastZindex: '',

rotate: 0,

lastRotate: 0,

visible: 3,

tracking: false,

animation: false,

currentPage: 0,

opacity: 1,

lastOpacity: 0,

swipe: false,

zIndex: 10

}

}

},

computed: {

// 划出面积比例

offsetRatio () {

let width = this.$el.offsetWidth

let height = this.$el.offsetHeight

let offsetWidth = width - Math.abs(this.temporaryData.poswidth)

let offsetHeight = height - Math.abs(this.temporaryData.posheight)

let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0

return ratio > 1 ? 1 : ratio

},

// 划出宽度比例

offsetWidthRatio () {

let width = this.$el.offsetWidth

let offsetWidth = width - Math.abs(this.temporaryData.poswidth)

let ratio = 1 - offsetWidth / width || 0

return ratio

}

},

mounted () {

// 绑定事件

this.$on('next', () => {

this.next()

})

this.$on('prev', () => {

this.prev()

})

},

methods: {

touchstart (e) {

if (this.temporaryData.tracking) {

return

}

// 是否为touch

if (e.type === 'touchstart') {

if (e.touches.length > 1) {

this.temporaryData.tracking = false

return

} else {

// 记录起始位置

this.basicdata.start.t = new Date().getTime()

this.basicdata.start.x = e.targetTouches[0].clientX

this.basicdata.start.y = e.targetTouches[0].clientY

this.basicdata.end.x = e.targetTouches[0].clientX

this.basicdata.end.y = e.targetTouches[0].clientY

// offsetY在touch事件中没有,只能自己计算

this.temporaryData.offsetY = e.targetTouches[0].pageY - this.$el.offsetParent.offsetTop

}

// pc操作

} else {

this.basicdata.start.t = new Date().getTime()

this.basicdata.start.x = e.clientX

this.basicdata.start.y = e.clientY

this.basicdata.end.x = e.clientX

this.basicdata.end.y = e.clientY

this.temporaryData.offsetY = e.offsetY

}

this.temporaryData.isStackClick = true

this.temporaryData.tracking = true

this.temporaryData.animation = false

},

touchmove (e) {

this.temporaryData.isStackClick = false

// 记录滑动位置

if (this.temporaryData.tracking && !this.temporaryData.animation) {

if (e.type === 'touchmove') {

e.preventDefault()

this.basicdata.end.x = e.targetTouches[0].clientX

this.basicdata.end.y = e.targetTouches[0].clientY

} else {

e.preventDefault()

this.basicdata.end.x = e.clientX

this.basicdata.end.y = e.clientY

}

// 计算滑动值

this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x

this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y

let rotateDirection = this.rotateDirection()

let angleRatio = this.angleRatio()

this.temporaryData.rotate = rotateDirection * this.offsetWidthRatio * 15 * angleRatio

}

},

touchend (e, index) {

if(this.temporaryData.isStackClick) {

this.$emit('click', index)

this.temporaryData.isStackClick = false

}

this.temporaryData.isStackClick = true

this.temporaryData.tracking = false

this.temporaryData.animation = true

// 滑动结束,触发判断

// 判断划出面积是否大于0.4

if (this.offsetRatio >= 0.4) {

// 计算划出后最终位置

let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth)

this.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 : this.temporaryData.poswidth - 200

this.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData.poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)

this.temporaryData.opacity = 0

this.temporaryData.swipe = true

this.nextTick()

// 不满足条件则滑入

} else {

this.temporaryData.poswidth = 0

this.temporaryData.posheight = 0

this.temporaryData.swipe = false

this.temporaryData.rotate = 0

}

},

nextTick () {

// 记录最终滑动距离

this.temporaryData.lastPosWidth = this.temporaryData.poswidth

this.temporaryData.lastPosHeight = this.temporaryData.posheight

this.temporaryData.lastRotate = this.temporaryData.rotate

this.temporaryData.lastZindex = 20

// 循环currentPage

this.temporaryData.currentPage = this.temporaryData.currentPage === this.pages.length - 1 ? 0 : this.temporaryData.currentPage + 1

// currentPage切换,整体dom进行变化,把第一层滑动置最低

this.$nextTick(() => {

this.temporaryData.poswidth = 0

this.temporaryData.posheight = 0

this.temporaryData.opacity = 1

this.temporaryData.rotate = 0

})

},

onTransitionEnd (index) {

let lastPage = this.temporaryData.currentPage === 0 ? this.pages.length - 1 : this.temporaryData.currentPage - 1

// dom发生变化正在执行的动画滑动序列已经变为上一层

if (this.temporaryData.swipe && index === lastPage) {

this.temporaryData.animation = true

this.temporaryData.lastPosWidth = 0

this.temporaryData.lastPosHeight = 0

this.temporaryData.lastOpacity = 0

this.temporaryData.lastRotate = 0

this.temporaryData.swipe = false

this.temporaryData.lastZindex = -1

}

},

prev () {

this.temporaryData.tracking = false

this.temporaryData.animation = true

// 计算划出后最终位置

let width = this.$el.offsetWidth

this.temporaryData.poswidth = -width

this.temporaryData.posheight = 0

this.temporaryData.opacity = 0

this.temporaryData.rotate = '-3'

this.temporaryData.swipe = true

this.nextTick()

},

next () {

this.temporaryData.tracking = false

this.temporaryData.animation = true

// 计算划出后最终位置

let width = this.$el.offsetWidth

this.temporaryData.poswidth = width

this.temporaryData.posheight = 0

this.temporaryData.opacity = 0

this.temporaryData.rotate = '3'

this.temporaryData.swipe = true

this.nextTick()

},

rotateDirection () {

if (this.temporaryData.poswidth <= 0) {

return -1

} else {

return 1

}

},

angleRatio () {

let height = this.$el.offsetHeight

let offsetY = this.temporaryData.offsetY

let ratio = -1 * (2 * offsetY / height - 1)

return ratio || 0

},

inStack (index, currentPage) {

let stack = []

let visible = this.temporaryData.visible

let length = this.pages.length

for (let i = 0; i < visible; i++) {

if (currentPage + i < length) {

stack.push(currentPage + i)

} else {

stack.push(currentPage + i - length)

}

}

return stack.indexOf(index) >= 0

},

// 非首页样式切换

transform (index) {

let currentPage = this.temporaryData.currentPage

let length = this.pages.length

let lastPage = currentPage === 0 ? this.pages.length - 1 : currentPage - 1

let style = {}

let visible = this.temporaryData.visible

if (index === this.temporaryData.currentPage) {

return

}

if (this.inStack(index, currentPage)) {

let perIndex = index - currentPage > 0 ? index - currentPage : index - currentPage + length

style['opacity'] = '1'

style['transform'] = 'translate3D(0,0,' + -1 * 60 * (perIndex - this.offsetRatio) + 'px' + ')'

style['zIndex'] = visible - perIndex

if (!this.temporaryData.tracking) {

style['transitionTimingFunction'] = 'ease'

style['transitionDuration'] = 300 + 'ms'

}

} else if (index === lastPage) {

style['transform'] = 'translate3D(' + this.temporaryData.lastPosWidth + 'px' + ',' + this.temporaryData.lastPosHeight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.lastRotate + 'deg)'

style['opacity'] = this.temporaryData.lastOpacity

style['zIndex'] = this.temporaryData.lastZindex

style['transitionTimingFunction'] = 'ease'

style['transitionDuration'] = 300 + 'ms'

} else {

style['zIndex'] = '-1'

style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'

}

return style

},

// 首页样式切换

transformIndex (index) {

if (index === this.temporaryData.currentPage) {

let style = {}

style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData.posheight + 'px' + ',0px) ' + 'rotate(' + this.temporaryData.rotate + 'deg)'

style['opacity'] = this.temporaryData.opacity

style['zIndex'] = 10

if (this.temporaryData.animation) {

style['transitionTimingFunction'] = 'ease'

style['transitionDuration'] = (this.temporaryData.animation ? 300 : 0) + 'ms'

}

return style

}

},

}

}

</script>

组件中分别实现了touchmouse事件,支持移动端和PC端滑动。

另外,点击卡片可以跳转到卡片详情页面。

okay,代码中都有一些备注,这里不作详细讲解了。有兴趣的可以去试一试哈~~

今天就分享到这里。希望对大家有所帮助!

vue使用vant下拉框van-dropdown-item 绑定title值不变问题

这篇文章主要介绍了解决vue使用vant下拉框van-dropdown-item 绑定title值不变问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

1、创建vue项目

2、使用vant组件

npm install vant --S

全局引用时在main.js引入

import Vant from \'vant\';

import \'vant/lib/index.css\';

Vue.use(Vant);

假如你引入之后发现页面的样式和组件都挂载了,但是console控制台会报错,说xxxx组件没有register,这个时候很有可能是你的vant插件版本有问题,重新下载一个最新的vant就可以了,现在是2.6.0版本

好,接下来继续

在需要使用下拉框的地方使用下拉框组件

<van-dropdown-menu>

<van-dropdown-item

v-model=\"value\"

:options=\"developList\"

/>

</van-dropdown-menu>

data () {

return {

value: \'\'

developList: [

{

value: \'1\',

text: \'我是第一个\'

},

{

value: \'2\',

text: \'我是第二个\'

},

]

}

}

假如是这样的话那么下拉框就会默认显示第一个字眼“wishing第一个”,然后你在点击下拉框选择第二个时也会改变成“我是第二个”

如果你不想有默认选中,并且一开始就显示请选择这样的提示字眼,那么我们可以去看看vant的api文档,发现有一个title的字眼,这个title就是下拉框的显示文字,一开始很多人以为这个下拉框的title只是用来显示然后点击下拉框的item之后会自动绑定过的,但其实是错误的,以下就是一个很好的例子

<van-dropdown-menu>

<van-dropdown-item

title=\"请选择\"

v-model=\"value\"

:disabled=\"disabled\"

:options=\"developList\"

/>

</van-dropdown-menu>

data () {

return {

value: \'\'

developList: [

{

value: \'1\',

text: \'我是第一个\'

},

{

value: \'2\',

text: \'我是第二个\'

},

]

}

}

你会发现请选择的自然从来没变过,无论你选择了第一个还是第二个,那么你就会想title是不是绑定,接下来就有以下操作

<van-dropdown-menu>

<van-dropdown-item

:title=\"title\"

v-model=\"value\"

:disabled=\"disabled\"

:options=\"developList\"

/>

</van-dropdown-menu>

data () {

return {

value: \'\'

title:\'\'请选择,

developList: [

{

value: \'1\',

text: \'我是第一个\'

},

{

value: \'2\',

text: \'我是第二个\'

},

]

}

}

加下来发现还是没有改动啊,那是不是绑定的值没有发生改变了,好像是的,因为你值定义了这个title,这个title就是下拉框选择的显示,只是你没有title的时候vant把你选择的text文字映射到title上去了,一旦你自己使用title进行绑定,那么每次修改时就要修改title,查看vant api可以发现有change事件,接下来就可以操作了

<van-dropdown-menu>

<van-dropdown-item

:title=\"title\"

v-model=\"value\"

:options=\"developList\"

@change=\"changeDevelop\"

/>

</van-dropdown-menu>

data () {

return {

value: \'\'

title:\'\'请选择,

developList: [

{

value: \'1\',

text: \'我是第一个\'

},

{

value: \'2\',

text: \'我是第二个\'

},

]

}

},

methods: {

changeDevelop (i) {

this.title = this.developList[i-1].text

},

}

这就没问题啦!

以上这篇解决vue使用vant下拉框van-dropdown-item 绑定title值不变问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。

vue使用vant下拉框van-dropdown-item 绑定title值不变问题 (https://www.wpmee.com/) javascript教程 第1张

标签:

提交需求或反馈

Demand feedback