HTML
<view wx:if="{{advertiseFlag}}" class="advertise-wrapper" style="background-color:{{transitionData.statusBtn == 'playing'?'rgba(255,255,255,0)':''}}" bindtap="jumpFn"><view class="advertise-box" style="width:{{transitionData.width}};height:{{transitionData.height}};left:{{transitionData.left}};top:{{transitionData.top}};opacity:{{transitionData.opacity}};animation:{{transitionData.animation}}"><image data-status="{{transitionData.statusBtn}}" catchtap="handleJumpValue" src="{{ advertiseMsg.url || 'https://yizhen-mamapai-dev.oss-cn-zhangjiakou.aliyuncs.com/certification/2024-06-13/b1791b525e974c0aae8a0c82a8410a9b.png'}}"></image><view class="jump-box" catchtap="jumpFn" data-status="{{transitionData.statusBtn}}">跳过{{defaultTime?defaultTime:''}}</view></view></view>
CSS
.advertise-wrapper {width: 100%;height: 100vh;background: rgba(0, 0, 0, 0.75);position: fixed;top: 0;left: 0;z-index: 999;display: flex;justify-content: center;align-items: center;
}.advertise-box {width: 580rpx;height: 980rpx;position: absolute;transition: all 1s linear;
}@keyframes shrinkAndMoveToPosition {from {transform: scale(1);opacity: 1;}to {transform: scale(0.5);opacity: 0;}
}.advertise-box image {width: 100%;height: 100%;
}.jump-box {background: rgba(0, 0, 0, 0.8);border-radius: 10px;padding: 4rpx 16rpx;position: absolute;top: 20rpx;right: 20rpx;color: #fff;font-size: 12px;
}
JS
动画函数
options的参数
from : 起始值 比如:0
to : 结束值 比如:100
totalMS :变化总时间 比如: 1000
duration : 每多少秒变化的次数 比如: 1
onmove :开始移动的回调函数
onend :移动结束的回调函数
let timer;function createAnimation(option) {// 起始值、结束值、变化总时间var {from,to,totalMS,duration,onmove,onend} = option;totalMS = totalMS || 1000;duration = duration || 10; // 每多少时间变化一次var times = Math.floor(totalMS / duration); // 变化的次数var dis = (to - from) / times; // 每次变化的量var curTimes = 0;// 每次变化的函数var timer = setInterval(() => {from += dis;curTimes++;// 变化完成,这里保证onmove 在 onend以前执行if (curTimes >= times) {from = to;onmove && onmove(from);onend && onend();clearInterval(timer);return;}onmove && onmove(from);}, duration);
}
获取dom
我们点击跳转的时候,首先需要获取到当前点击 dom 的 status,如果当前的状态为 playing 直接 return,否则开始获取当前的 dom 信息,找到当前点击的 dom 和所要跳转到的 dom 所在位置,然后找到所要跳转的位置后,把当前点击的dom和所要去的dom传给开始的动画函数
handleGetDom(type) {if (!type || type <= 0) returnlet _this = thiswx.createSelectorQuery().select('.advertise-box').boundingClientRect().selectAll('.grid-container .item').boundingClientRect().exec((ret) => {const [popRect, endDoms] = ret;const targetIndex = endDoms.findIndex((item) => item.id == type);if (targetIndex === -1) return;const endDom = endDoms[targetIndex];_this.startTransition(popRect, endDom);})},
开始动画过渡
根据获取 dom 和所要去的 dom 的位置,在拿到要结束 dom 之前先把 status 状态设置为 playing ,这样后我们就可以设置动画效果然后把对应的参数传给动画函数 createAnimation 。
// 开始动画过渡startTransition(popRect, endDom) {const _this = this;// 设置点击状态为playing_this.setData({transitionData: {..._this.data.transitionData,statusBtn: 'playing'}});const centerX = endDom.leftconst centerY = endDom.top_this.setData({transitionData: {..._this.data.transitionData,animation: "shrinkAndMoveToPosition 2s forwards"}});createAnimation({from: popRect.left,to: centerX,totalMS: 1000,onmove: (n) => {_this.updateTransitionData(endDom, centerX, centerY);},onend: () => {_this.endTransition(endDom);}});},
动画的更新函数
这个地方需要注意的是在支付宝中 left、top 不用需要加 px,width和height自行决定用不用除以2
// 更新动画过程中的数据updateTransitionData(endDom, centerX, centerY) {this.setData({transitionData: {...this.data.transitionData,width: `${endDom.width / 2}px`,height: `${endDom.height / 2}px`,left: `${centerX}px`,top: `${centerY}px`}});},
动画的结束函数
在动画结束的时候我们需要把 status 更改为 end , opacity 设置为 0,清除定时器就可以了
// 结束动画并处理跳转endTransition(endDom) {const _this = this;wx.showTabBar();_this.setData({transitionData: {..._this.data.transitionData,statusBtn: 'end',opacity: 0},advertiseFlag: false});clearInterval(timer);const currItem = {...endDom.dataset.item,richTextType: 2,appletAdvertisementId: endDom.dataset.item.type}_this.handleJumpTypePage(currItem);},
动画所有相关的事件函数
// 跳转类型handleJumpValue(e) {let {relationHomeSwitch,relationHomeType,id} = this.data.advertisingPopupthis.handleClickDataSave(id)if (relationHomeType && relationHomeSwitch == 1) {let status = e.currentTarget.dataset.status// 判断是否有点击过 statusBtnif (status == 'playing') return;this.handleGetDom(relationHomeType)} else if (relationHomeSwitch == 2) {let data = {...this.data.advertisingPopup,richTextType: 3,}this.handleJumpTypePage(data)} else {wx.showTabBar();this.setData({advertiseFlag: false});clearInterval(timer);}},// 获取domhandleGetDom(type) {if (!type || type <= 0) returnlet _this = thiswx.createSelectorQuery().select('.advertise-box').boundingClientRect().selectAll('.grid-container .item').boundingClientRect().exec((ret) => {const [popRect, endDoms] = ret;const targetIndex = endDoms.findIndex((item) => item.id == type);if (targetIndex === -1) return;const endDom = endDoms[targetIndex];_this.startTransition(popRect, endDom);})},// 开始动画过渡startTransition(popRect, endDom) {const _this = this;// 设置点击状态为playing_this.setData({transitionData: {..._this.data.transitionData,statusBtn: 'playing'}});const centerX = endDom.leftconst centerY = endDom.top_this.setData({transitionData: {..._this.data.transitionData,animation: "shrinkAndMoveToPosition 2s forwards"}});createAnimation({from: popRect.left,to: centerX,totalMS: 1000,onmove: (n) => {_this.updateTransitionData(endDom, centerX, centerY);},onend: () => {_this.endTransition(endDom);}});},// 更新动画过程中的数据updateTransitionData(endDom, centerX, centerY) {this.setData({transitionData: {...this.data.transitionData,width: `${endDom.width / 2}px`,height: `${endDom.height / 2}px`,left: `${centerX}px`,top: `${centerY}px`}});},// 结束动画并处理跳转endTransition(endDom) {const _this = this;wx.showTabBar();_this.setData({transitionData: {..._this.data.transitionData,statusBtn: 'end',opacity: 0},advertiseFlag: false});clearInterval(timer);const currItem = {...endDom.dataset.item,richTextType: 2,appletAdvertisementId: endDom.dataset.item.type}_this.handleJumpTypePage(currItem);},// 点击handleHomeConfigClick(id) {let params = {id: id}api.post('/main-service/home-config/click', params).then((res) => {if (res.code == 1) {console.log(res, 'res');}}).catch((err) => {console.log(err, 'err');})},jumpFn() {wx.showTabBar();this.setData({advertiseFlag: false});clearInterval(timer);},// 动画end
完整实现代码
// pages/prize/prize.js
const api = require('../../common/api')
const App = getApp()
const getAuthCode = require('../../common/authen.js').getAuthCodelet timer;
let count = 0;function createAnimation(option) {// 起始值、结束值、变化总时间var {from,to,totalMS,duration,onmove,onend} = option;totalMS = totalMS || 1000;duration = duration || 10; // 没多少时间变化一次var times = Math.floor(totalMS / duration); // 变化的次数var dis = (to - from) / times; // 每次变化的量var curTimes = 0;// 每次变化的函数var timer = setInterval(() => {from += dis;curTimes++;// 变化完成,这里保证onmove 在 onend以前执行if (curTimes >= times) {from = to;onmove && onmove(from);onend && onend();clearInterval(timer);return;}onmove && onmove(from);}, duration);
}Page({data: {transitionData: {statusBtn: '', // 是否已经点击过left: '',top: '',width: '580rpx',height: '980rpx',opacity: 1},marketingId: "", // 营销idstyleConfigData: {},imgSrc: [],paramStr: '',indicatorDots: false,autoplay: true,vertical: false,interval: 2000,circular: true,duration: 1500,defaultTime: 4, //默认时间advertiseFlag: false,advertiseMsg: {},activityData: {sourceType: ''},styleHomeImage: {},activeIndex: "", // 切换下标interval: 3000,advertisingRotation: [], // 广告位轮播newHomepage: [],},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {// this.getImage()// this.getStyleConfig()this.getAuth()this.getHomeConfigPage()this.handleGetAxiosData()this.getAdvertisement()},onShow() {let token = wx.getStorageSync('token')if (App.globalData.activityData.sourceType == 'activity' && App.globalData.inviteCustomerNum < 2 && token) {this.handleActivity()}},handleActivity() {const params = App.globalData.activityDataapi.post('/main-service/customer-invite-record', params).then((res) => {if (res.code === 1) {console.log('邀请处理成功')App.globalData.inviteCustomerNum = 2}if (res.code == 1010002) {console.log('邀请处理失败')App.globalData.inviteCustomerNum = 1}})},// 获取首页头部配置getHomeConfigPage() {api.get("/main-service/home-config/list").then((res) => {if (res.code == 1) {let originalHomepage = res.data && res.data.sort((a, b) => {return a.type - b.type}) || []// 处理数据,添加额外属性用于渲染const processedHomepage = originalHomepage.map(item => {return {...item,shouldDisplay: this.shouldDisplayItem(item),className: this.getClassByType(item.type),imageMode: this.getImageMode(item.type),showMenu: item.type >= 5,defaultImage: this.getDefaultImage(item.type)};});this.setData({newHomepage: processedHomepage})}}).catch((err) => {console.log(err, 'err');})},// 获取页面样式配置getStyleConfig() {api.get('/main-service/activity_style_config').then((res) => {if (res.code === 1) {const {data} = resthis.setData({styleConfigData: {...data,topImg: res.data.imgUrl.split(",")[0],bottomImg: res.data.imgUrl.split(",")[1],}})}})},//数据handleGetAxiosData() {let params = {type: 39}api.get(`/main-service/sys/info`, params).then((res) => {console.log(res, 'res')if (res.code == 1) {this.setData({styleHomeImage: res.data.remark ? JSON.parse(res.data.remark) : {},})}})},getImage() {api.get('/main-service/home-page/advertising').then(res => {this.setData({imgSrc: res.data.weChatBackgroundUrl})})},// 获取 广告位轮播图getAdvertisement() {const params = {position: 1}api.get('/main-service/advertisement', params).then((res) => {if (res.code == 1) {this.setData({advertisingRotation: res.data})}})},// 改变图片changeImg(e) {this.activeIndex = e.detail.currentthis.setData({activeIndex: e.detail.current})},getAuth() {getAuthCode().then(res => {let authCode = res.authCode;let params = {code: authCode};this.getAdvertiseMsg(params);});},//获取弹窗广告信息getAdvertiseMsg(params) {api.get("/main-service/applet-advertisement/v2", params).then(res => {if (res.code == 1) {this.setData({advertiseFlag: res.data.status == 1 ? true : false,advertiseMsg: res.data,advertisingPopup: {...res.data}});if (res.data.id) {wx.hideTabBar();}if (res.data.status == 1) {timer = setInterval(() => {if (this.data.defaultTime > 0) {let time = (this.data.defaultTime -= 1);this.setData({defaultTime: time});return;}if (!this.data.defaultTime) {clearInterval(timer);this.isLike();}}, 1000);}}});},//是否感兴趣isLike() {let id = this.data.advertiseMsg.id;if (id) {api.get(`/main-service/applet-advertisement/${id}`).then(res => {if (res.code == 1) {console.log("感兴趣");}});}},// 跳转详情handleJumpDetails(item) {let {id,jumpType} = item.currentTarget.dataset.valueif (jumpType == 13) {wx.navigateTo({url: `/addressManagement/pages/richText/richText?jumpId=${id}`});}},// 页面跳转handleJumpToPage(e) {let {item} = e.currentTarget.datasetthis.handleHomeConfigClick(item.id)wx.hideLoading()let data = {...item,richTextType: 2,appletAdvertisementId: item.type}wx.hideLoading()this.handleJumpTypePage(data)},// 根据跳转类型处理页面跳转handleJumpTypePage(item) {const jumpType = Number(item.jumpType);this.jumpFn()const routes = {1: () => this.handlePageTo(),2: () => wx.navigateTo({url: "/addressManagement/pages/invitingWithCourtesy/invitingWithCourtesy"}),3: () => wx.navigateTo({url: `/addressManagement/pages/invitationGiftDetail/invitationGiftDetail?id=${item.jumpValue}`}),4: () => wx.switchTab({url: '/pages/activity/activity'}),5: () => wx.navigateTo({url: `/pages/activityDetail/activityDetail?activityId=${item.jumpValue}`}),6: () => wx.navigateTo({url: `/addressManagement/pages/richText/richText?jumpId=${item.appletAdvertisementId}&type=${item.richTextType}`}),// 7: () => wx.navigateTo({ url: `/pages/content-detail/content-detail?id=${item.jumpValue}&type=1` }),// 8: () => wx.navigateTo({ url: `/pages/content-detail/content-detail?id=${item.jumpValue}&type=3` }),9: () => wx.navigateTo({url: `/addressManagement/pages/richText/richText?jumpId=${item.appletAdvertisementId}&status=btn&type=${item.richTextType}`}),10: () => wx.navigateTo({url: `/addressManagement/pages/marketing/marketing?id=${item.type}&type=home`}),};if (routes[jumpType]) {routes[jumpType]();}},// 点击数据handleClickDataSave(id) {let params = {appletAdvertisementCustomerRecordId: id}api.get('/main-service/applet-advertisement/save-click-data', params).then((res) => {if (res.code == 1) {console.log(res, 'res');}}).catch((err) => {console.log(err, 'err');})},// 跳转类型handleJumpValue(e) {let {relationHomeSwitch,relationHomeType,id} = this.data.advertisingPopupthis.handleClickDataSave(id)if (relationHomeType && relationHomeSwitch == 1) {let status = e.currentTarget.dataset.status// 判断是否有点击过 statusBtnif (status == 'playing') return;this.handleGetDom(relationHomeType)} else if (relationHomeSwitch == 2) {let data = {...this.data.advertisingPopup,richTextType: 3,}this.handleJumpTypePage(data)} else {wx.showTabBar();this.setData({advertiseFlag: false});clearInterval(timer);}},// 获取domhandleGetDom(type) {if (!type || type <= 0) returnlet _this = thiswx.createSelectorQuery().select('.advertise-box').boundingClientRect().selectAll('.grid-container .item').boundingClientRect().exec((ret) => {const [popRect, endDoms] = ret;const targetIndex = endDoms.findIndex((item) => item.id == type);if (targetIndex === -1) return;const endDom = endDoms[targetIndex];_this.startTransition(popRect, endDom);})},// 开始动画过渡startTransition(popRect, endDom) {const _this = this;// 设置点击状态为playing_this.setData({transitionData: {..._this.data.transitionData,statusBtn: 'playing'}});const centerX = endDom.leftconst centerY = endDom.top_this.setData({transitionData: {..._this.data.transitionData,animation: "shrinkAndMoveToPosition 2s forwards"}});createAnimation({from: popRect.left,to: centerX,totalMS: 1000,onmove: (n) => {_this.updateTransitionData(endDom, centerX, centerY);},onend: () => {_this.endTransition(endDom);}});},// 更新动画过程中的数据updateTransitionData(endDom, centerX, centerY) {this.setData({transitionData: {...this.data.transitionData,width: `${endDom.width / 2}px`,height: `${endDom.height / 2}px`,left: `${centerX}px`,top: `${centerY}px`}});},// 结束动画并处理跳转endTransition(endDom) {const _this = this;wx.showTabBar();_this.setData({transitionData: {..._this.data.transitionData,statusBtn: 'end',opacity: 0},advertiseFlag: false});clearInterval(timer);const currItem = {...endDom.dataset.item,richTextType: 2,appletAdvertisementId: endDom.dataset.item.type}_this.handleJumpTypePage(currItem);},// 点击handleHomeConfigClick(id) {let params = {id: id}api.post('/main-service/home-config/click', params).then((res) => {if (res.code == 1) {console.log(res, 'res');}}).catch((err) => {console.log(err, 'err');})},jumpFn() {wx.showTabBar();this.setData({advertiseFlag: false});clearInterval(timer);},// 动画endhandlePageTo: function () {let that = thiswx.showLoading({title: '加载中',mask: true})this.checkAttestation()// that.getUserInfo()},// getUserInfo() {// let that = this// getAuthCode().then((res) => {// const {// authCode// } = res// let params = {// loginType: 7,// isRelatedPhoneNumber: 0,// grantCode: authCode// }// api.post('/main-service/customer/login', params).then(({// data// }) => {// const {// isRelatedPhoneNumber,// phoneNumber,// token// } = data// if (isRelatedPhoneNumber == 0) {// wx.hideLoading()// wx.reLaunch({// url: '/pages/login/login?sourceType=prize'// });// return// }// if (isRelatedPhoneNumber == 1) {// wx.hideLoading()// wx.setStorageSync('token', token)// that.checkAttestation()// return// }// })// })// },checkAttestation() {api.get('/main-service/pregnant-certification/status').then(({data}) => {wx.hideLoading()const {isWhite,isSellOut,status,identityType,isSyncTaoBao} = dataif (isWhite == 0) {if (status == 0) {// 跳转认证页面wx.navigateTo({url: '/pages/authentication/authentication'})}if (status == 1) {// 跳转认证中wx.navigateTo({url: '/pages/AddCustomer/AddCustomer'})}if (status == 2) {// 打开领取链接if (isSellOut) {// 礼品售罄// "identityType": 1:孕妈 2:宝妈if (!isSyncTaoBao) {wx.navigateTo({url: '/pages/sellOut/sellOut'})// if (identityType == 1) {// wx.navigateTo({// url: '/pages/sellOut/sellOut'// })// }// if (identityType == 2) {// wx.navigateTo({// url: `/pages/certificationPassed/certificationPassed`// })// }} else {// 修改之后的逻辑wx.navigateTo({url: `/pages/giftGuide/giftGuide?status=${status}`})}} else {wx.navigateTo({url: `/pages/giftGuide/giftGuide?status=${status}`})// if (identityType == 1) {// wx.navigateTo({// url: `/pages/giftGuide/giftGuide?status=${status}`// })// }// if (identityType == 2) {// wx.navigateTo({// url: `/pages/certificationPassed/certificationPassed`// })// }}}if (status == 3) {// 跳转拒绝页面wx.navigateTo({url: '/pages/certificationReject/certificationReject'})}if (status == 4) {// 跳转退回页面重新编辑wx.navigateTo({url: `/pages/authentication/authentication?status=${status}`})}}if (isWhite == 1) {if (isSellOut) {wx.navigateTo({url: '/pages/sellOut/sellOut'})} else {wx.navigateTo({url: `/pages/giftGuide/giftGuide?status=${status}`})}}})},handleClickBanner() {wx.navigateTo({url: '/addressManagement/pages/bannerDetail/bannerDetail',})},// 跳转会场handleToShare() {wx.navigateTo({url: `/addressManagement/pages/taobaoVenue/taobaoVenue?type=home`,});},// 开启分享onShareAppMessage() {},// 判断是否展示该 itemshouldDisplayItem(item) {if (item.type >= 5 && item.showSwitch !== 1) return false;return !!item.url || item.type <= 4; // 当 type 小于等于 4 时总是展示},// 根据 type 返回对应的类名getClassByType(type) {switch (type) {case 1:return 'new-mom-gift';case 2:return 'xhs-volunteer';case 3:return 'douyin-volunteer';case 4:return 'invitation-gift';case 5:return 'advertising-space-one';case 6:return 'advertising-space-two';case 7:return 'advertising-space-three';default:return '';}},// 根据 type 返回图片的 modegetImageMode(type) {return type >= 5 ? 'widthFix' : 'scaleToFill';},// 获取默认图片 URLgetDefaultImage(type) {return 'https://yizhen-mamapai-dev.oss-cn-zhangjiakou.aliyuncs.com/certification/2024-06-13/3cb773c6e3614c389619a24d55f868d4.png';},onPullDownRefresh() {Promise.all([this.getHomeConfigPage()]).then(res => {this.stopPullDownRefresh();});},onUnload() {this.jumpFn()},})