进入下面小程序可以体验效果:
以下皆基于官方原版demo修改
官方原版demo是使用sticky-header做的吸顶,如果是不涉及input普通使用其实这篇内容可以放弃了,直接用官方即可。
如是需要用到input框吸顶,且需要避免点击input框时scroll-view会自动回顶的问题。那么请看下面代码!
WXML:
<view class="search-container">
<scroll-tab class="in-bar"
style="position: absolute;height: 34px;"
iconDirect="left"
bind:tapIcon="tapIcon"></scroll-tab>
<view class="search-p">
<view class="search">
<view class="search-icon-wrp">
<image class="search-icon" src="/assets/image/search.png" />
</view>
<input class="search-text"
confirm-type="search"
value="{{searchValue}}"
bindinput="searchBlur" bindconfirm="toSearch" placeholder="你想要的都能搜到哟~" >搜索</input>
</view>
</view>
</view>
<scroll-view
class="scroll-area"
type="custom"
enable-back-to-top="{{false}}"
scroll-y
scroll-with-animation="{{true}}"
show-scrollbar="{{false}}"
bindscrolltolower="lower"
lower-threshold='150'
worklet:onscrollend="handleScrollEnd"
worklet:onscrollupdate="handleScrollUpdate"
>
<view class="fake-nav-bar" style="height: {{menuBottom+60}}px;"/>
<grid-builder type="masonry"
list="{{gridList[0]}}"
child-count="{{gridList[0]}}"
cross-axis-count="{{2}}" cross-axis-gap="{{10}}" main-axis-gap="{{10}}" padding="{{[4,10,0,10]}}">
<block slot:item slot:index>
<view style="width: 100%;height: {{item.height}}px;border: 1px solid gainsboro;">
<image style="width: 100%;height: 100%;" mode="aspectFill" src="https://img-blog.csdnimg.cn/direct/25e4d10c5187409d9190a2f47297503e.jpeg"></image>
</view>
</block>
</grid-builder>
<view style="height: 310rpx;margin-bottom:calc(80rpx + env(safe-area-inset-bottom) / 2);"></view>
</scroll-view>
js:
const app = getApp() //这段代码需要放在page页面JS的最顶部
const { shared, Easing,spring} = wx.worklet
const lerp = function (begin, end, t) {
'worklet'
return begin + (end - begin) * t
}
const clamp = function (cur, lowerBound, upperBound) {
'worklet'
if (cur > upperBound) return upperBound
if (cur < lowerBound) return lowerBound
return cur
}
Page({
/**
* 页面的初始数据
*/
data: {
paddingTop:app.globalData.menu.top,
menuBottom:app.globalData.menu.bottom,
gridList:[[]],
searchValue:''
},
searchBlur: function (e) {
this.setData({
searchValue: e.detail.value,
})
this.currentName=e.detail.value
},
/**
* 搜索头像
*/
toSearch(e){
this.setData({
gridList:[[]]
})
this.loadData();
},
lower(e){
//触底加载数量
const that = this;
// 加载数据
setTimeout(function() {
that.loadData();
}, 1000);
},
/**
* 加载瀑布数据
* 分类壁纸
*/
loadData(){
let nowList = `gridList[0]`
var list = [];
for(var i=0;i<50;i++){
list.push({
"height":this.getRadomHeight()
})
}
let newList = this.data.gridList[0].concat(list)
this.setData({
[nowList]:newList
})
},
/**
* 生成随机(100, 400)高度
*/
getRadomHeight() {
return parseInt(Math.random()*100 + 100)
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.loadData();
this.applyStyle()
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 应用搜索框动画
*/
applyStyle(){
this.saftPadding = shared(app.globalData.menu.top)
//用于计算输入框的宽度到胶囊左边的距离
this.menuWidth =shared((((app.globalData.menu.left)/app.globalData.system.windowWidth)*100)-15)
this.menuBottom =shared(app.globalData.menu.height+15)
this.menuBottom2 =shared(app.globalData.menu.height+15)
this.searchBarheight = shared(app.globalData.menu.height)
this.searchBarWidth = shared(100)
this.scrollBarWidth = shared(72)
this.searchBarLeft = shared(0)
this.navBarOpactiy = shared(1)
this.wnavBarOpactiy = shared(1)
this.inBarOpactiy = shared(0)
this.paddingLeft = shared(10)
this.paddingRight = shared(10)
this.navBarZindex = shared(10)
this.navBarLeft = shared(app.globalData.system.windowWidth-app.globalData.menu.right)
this.isTop = shared(false) //是否触顶了
this.isBottom = shared(false) //是否触底了
this.isExc = shared(false) //是否执行动画了
this.isScrolling = shared(false) //是否正在滑动
this.applyAnimatedStyle('.nav-bar', () => {
'worklet'
return {
marginTop: `${this.saftPadding.value}px`,
height: `${this.searchBarheight.value}px`,
width: `${this.scrollBarWidth.value}%`,
opacity: this.wnavBarOpactiy.value,
zIndex:this.navBarZindex.value,
marginLeft:`${this.navBarLeft.value}px`
}
})
this.applyAnimatedStyle('.in-bar', () => {
'worklet'
return {
height: `${this.searchBarheight.value}px`,
width: `${this.scrollBarWidth.value}%`,
opacity: this.inBarOpactiy.value,
marginLeft:`${this.navBarLeft.value}px`
}
})
this.applyAnimatedStyle('.search', () => {
'worklet'
return {
width: `${this.searchBarWidth.value}%`,
height: `${this.searchBarheight.value}px`,
marginTop: `${this.menuBottom.value}px`
}
})
this.applyAnimatedStyle('.search-p', () => {
'worklet'
return {
paddingLeft:`${this.paddingLeft.value}px`,
paddingRight:`${this.paddingRight.value}px`,
left: `${this.searchBarLeft.value}%`,
}
})
this.applyAnimatedStyle('.search-container', () => {
'worklet'
return {
backgroundColor: (this.wnavBarOpactiy.value > 0.1) ? 'transparent' : '#fff',
paddingTop: `${this.saftPadding.value}px`,
position:'absolute',
width:'100%',
zIndex:99
}
})
},
/**
* 滚动列表Worklet动画事件
*/
handleScrollUpdate(evt) {
'worklet'
const maxw = 100;
const minw = this.menuWidth.value;
const maxDistance = 60
const scrollTop = clamp(evt.detail.scrollTop, 0, maxDistance)
const progress = scrollTop / maxDistance
const EasingFn = Easing.cubicBezier(0.4, 0.0, 0.2, 1.0)
this.searchBarLeft.value = lerp(0, 10, EasingFn(progress))
this.inBarOpactiy.value = lerp(0, 1, progress)
this.navBarOpactiy.value = lerp(1, 0, progress)
const t1= lerp(maxw, minw, EasingFn(progress)) //45参数不能动
this.menuBottom.value = lerp(this.menuBottom2.value, 0, EasingFn(progress))
this.paddingLeft.value = lerp(10, 0, EasingFn(progress))
this.paddingRight.value = lerp(10, 0, EasingFn(progress))
this.navBarZindex.value = lerp(10, 0, EasingFn(progress))
// this.scrollBarWidth.value = t2
if(t1>minw&&t1<maxw){
//这个范围表示正在滑行
this.wnavBarOpactiy.value=0
this.isScrolling.value=true
this.isBottom.value=false
this.isTop.value=false
}
if((t1>minw&&t1<maxw)&&(!this.isExc.value||this.isScrolling.value)){
//这个表示没有执行动画或者正在化学且范围内,将宽度计算减少1.5为了弹力效果
this.searchBarWidth.value = t1-1.5
}
if(t1===minw&&evt.detail.scrollTop>minw&&!this.isTop.value){
//靠顶了
this.isTop.value=true
this.isBottom.value=false
this.searchBarWidth.value = t1-2
this.searchBarWidth.value = spring(t1)
this.isExc.value=true
this.isScrolling.value=false
}else if(t1===maxw&&evt.detail.scrollTop<1&&!this.isBottom.value){
//靠底了
this.wnavBarOpactiy.value=1
this.isBottom.value=true
this.isTop.value=false
this.searchBarWidth.value = t1-2
this.searchBarWidth.value = spring(t1)
this.isExc.value=true
this.isScrolling.value=false
}
},
/**
* 滑动手离开后,停止了
*/
handleScrollEnd(evt){
'worklet'
this.isScrolling.value=false
this.isExc.value=false
},
/**
* 生命周期函数--监听页面显示
*/
onShow(){
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
WXSS:
.fake-nav-bar {
height: 60px;
}
.search-container {
padding: 0 0px 10px 0px;
}
.search {
display: flex;
flex-direction: row;
box-sizing: border-box;
width: 100%;
height: 40px;
border-radius: 20px;
border: 1px solid #dae0dd;
position: relative;
align-items: center;
background-color: #fff;
}
.search-text {
color: #1b1b1b;
font-size: 16px;
width: 100%;
height: 100%;
}
.search-icon-wrp {
display: flex;
width: 30px;
height: 100%;
flex-direction: row;
align-items: center;
justify-content: center;
}
.search-icon {
width: 16px;
height: 16px;
}
.search-btn {
position: absolute;
right: 0;
width: 60px;
height: 100%;
border-radius: 20px;
background-color: #07c160;
display: flex;
align-items: center;
justify-content: center;
color: #FFF;
font-size: 16px;
/* font-weight: bold; */
}
.scroll-area {
height: 100vh;
}
.grids {
width: 100%;
margin-bottom: 390rpx;
display: flex;
flex-flow: wrap;
}
.grids-cell {
width: 30%;
border-radius: 100px;
margin: 15rpx 7.5rpx;
text-align: center;
background: #f1f1f1;
padding: 15rpx 10rpx;
font-size: 28rpx;
font-family: fangsong;
}