- 小程序EventChannel的一些理解和使用实践
EventChannel是什么通过官方文档和示例,实际可以发现EventChannel借助wx.navigateTo方法,在两个页面之间构建起了数据通道,互相可以通过“派发事件”及“注册这些事件的监听器”来实现基于事件的页面通信。 具体看下下面的官方示例: wx.navigateTo({ url: 'test?id=1', events: { // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据 acceptDataFromOpenedPage: function(data) { console.log(data) }, someEvent: function(data) { console.log(data) } ... }, success: function(res) { // 通过 eventChannel 向被打开页面传送数据 res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' }) } }) events: 注册将在目标页面触发(派发)的同名事件的监听器 success:跳转后进行可通过res.eventChannel 触发自定义事件 //test.js Page({ onLoad: function(option){ console.log(option.query) const eventChannel = this.getOpenerEventChannel() eventChannel.emit('acceptDataFromOpenedPage', {data: 'test'}); eventChannel.emit('someEvent', {data: 'test'}); // 监听 acceptDataFromOpenerPage 事件,获取上一页面通过 eventChannel 传送到当前页面的数据 eventChannel.on('acceptDataFromOpenerPage', function(data) { console.log(data) }) } }) 通过this.getOpenerEventChannel()获取eventChannel对象, 使用eventChannel.emit() 可以在任意时刻触发(派发)事件。当前页面即可执行对应的事件处理函数。 使用eventChannel.on() 即可监听上个页面emit的事件。 使用示例A页面为订单页面,B页面为填写发票信息页面,A页面跳转B页面携带开票金额(amount),A页面如果已填写,再次跳转B页面,需要携带发票信息(invoice),方便直接编辑B页面填写后返回更新A页面的发票信息(invoice)简单的实现是 A到B的传值使用query实现,B到A的传值使用globalData实现。 // A页面 onShow(){ if(globalData.temp_invoice_info){ this.setData({ invoice:globalData.temp_invoice_info },()=>{ globalData.temp_invoice_info=null; }) } }, //跳转填写发票表单信息 toFillOutInvoiceInfo() { wx.navigateTo({ url: `/pages/invoice/invoice-info-form/invoice-info-form?amount=${ this.data.detail.amount }&invoice=${ this.data.invoice.invoice_title ? JSON.stringify(this.data.invoice) : "" }`, }); }, //B页面 onLoad(options){ this.data.options = options; if (this.data.options.invoice) { this.setData({ invoice: JSON.parse(this.data.options.invoice) }) } }, submit(){ globalData.temp_invoice_info = this.data.invoice; wx.navigateBack(); } 如下EventChannel的实现 //A页面(注意这里需要使用箭头函数(访问this)) //跳转填写发票表单信息 toFillOutInvoiceInfo() { wx.navigateTo({ url: `/pages/invoice/invoice-info-form/invoice-info-form`, events:{ updateInvoice:(result)=>{ this.setData({invoice:result}) } }, success:(res)=>{ const params = { amount: this.data.amount, invoice:this.data.invoice.invoice_title ? JSON.stringify(this.data.invoice) : "" } res.eventChannel.emit('sendQueryParams',params) } }); }, //B页面 onLoad(){ this.getOpenerEventChannel().once('sendQueryParams',(params)=>{ const {amount,invoice} = parmas; this.setData({amount,invoice}) }) } submit(){ this.getOpenerEventChannel().emit('updateInvoice',this.data.invoice); wx.navigateBack(); } 还有一个额外的地方就是EventChannel可以在A-B-C多个页面直接建立数据通道。官方示例。 //注意this.getOpenerEventChannel() 只能在navigateTo模板页面使用,其他更多页面使用时, //可以保存在getApp()全局实例中以备其他页面使用 // 保留AB通道事件,已备C页面给A页面发送数据 // B页面 const eventChannel = this.getOpenerEventChannel() getApp().pageBEventChannel = eventChannel //C页面 getApp().pageBEventChannel.emit('PageAacceptDataFromPageC', { data: 'Page C->A' }); 有不对或扩展的地方欢迎大家批评交流。
2022-08-11 - 微信小程序 Animation 的 timing function 如何用 贝塞尔曲线?
如题。似乎只能用CSS关键字
2022-08-07 - 【转】微信小程序中实现瀑布流布局和无限加载
瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。 在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形式。在微信小程序中,我们也可以做出这样的效果,不过由于小程序框架的一些特性,在实现思路上还是有一些差别的。 今天我们就来看一下如何在小程序中去实现这种瀑布流布局: [图片] 小程序瀑布流布局 我们要实现的是一个固定2列的布局,然后将图片数据动态加载进这两列中(而加载进来的图片,会根据图片实际的尺寸,来决定到底是放在左列还是右列中)。 [代码]/* 单个图片容器的样式 */.img_item { width: 48%; margin: 1%; display: inline-block; vertical-align: top; }[代码]我们知道,在HTML中,我们要动态加载图片的话,通常会使用new Image()创建一个图片对象,然后通过它来动态加载一个url指向的图片,并获取图片的实际尺寸等信息。而在小程序框架中,并没有提供相应的JS对象来处理图片加载。其实我们可以借助wxml中的[图片]组件来完成这样的功能,虽然有点绕,但还是能满足我们的功能要求的。 [代码]<view style="display:none"> <image wx:for="{{images}}" wx:key="id" id="{{item.id}}" src="{{item.pic}}" bindload="onImageLoad">image>view>[代码]我们可以在Page中通过数据绑定,来传递要加载的图片信息到wxml中,让[图片]组件去加载图片资源,然后当图片加载完成的时候,通过bindload指定的事件处理函数来做进一步处理。 我们来看一下Page文件中定义的onImageLoad函数。在其中,我们可以从传入的事件对象e上,获取到[图片]组件的丰富信息,包括通过它加载进来的图片的实际大小。然后我们将图片按照页面上实际需要显示的尺寸,计算出同比例缩放后的尺寸。接着,我们可以根据左右两列目前累积的内容高度,来决定把当前加载进来的图片放到哪一边。 [代码]let col1H = 0;let col2H = 0; Page({ data: { scrollH: 0, imgWidth: 0, loadingCount: 0, images: [], col1: [], col2: [] }, onLoad: function () { wx.getSystemInfo({ success: (res) => { let ww = res.windowWidth; let wh = res.windowHeight; let imgWidth = ww * 0.48; let scrollH = wh; this.setData({ scrollH: scrollH, imgWidth: imgWidth }); //加载首组图片 this.loadImages(); } }) }, onImageLoad: function (e) { let imageId = e.currentTarget.id; let oImgW = e.detail.width; //图片原始宽度 let oImgH = e.detail.height; //图片原始高度 let imgWidth = this.data.imgWidth; //图片设置的宽度 let scale = imgWidth / oImgW; //比例计算 let imgHeight = oImgH * scale; //自适应高度 let images = this.data.images; let imageObj = null; for (let i = 0; i < images.length; i++) { let img = images[i]; if (img.id === imageId) { imageObj = img; break; } } imageObj.height = imgHeight; let loadingCount = this.data.loadingCount - 1; let col1 = this.data.col1; let col2 = this.data.col2; //判断当前图片添加到左列还是右列 if (col1H <= col2H) { col1H += imgHeight; col1.push(imageObj); } else { col2H += imgHeight; col2.push(imageObj); } let data = { loadingCount: loadingCount, col1: col1, col2: col2 }; //当前这组图片已加载完毕,则清空图片临时加载区域的内容 if (!loadingCount) { data.images = []; } this.setData(data); }, loadImages: function () { let images = [ { pic: "../../images/1.png", height: 0 }, { pic: "../../images/2.png", height: 0 }, { pic: "../../images/3.png", height: 0 }, { pic: "../../images/4.png", height: 0 }, { pic: "../../images/5.png", height: 0 }, { pic: "../../images/6.png", height: 0 }, { pic: "../../images/7.png", height: 0 }, { pic: "../../images/8.png", height: 0 }, { pic: "../../images/9.png", height: 0 }, { pic: "../../images/10.png", height: 0 }, { pic: "../../images/11.png", height: 0 }, { pic: "../../images/12.png", height: 0 }, { pic: "../../images/13.png", height: 0 }, { pic: "../../images/14.png", height: 0 } ]; let baseId = "img-" + (+new Date()); for (let i = 0; i < images.length; i++) { images[i].id = baseId + "-" + i; } this.setData({ loadingCount: images.length, images: images }); } })[代码]这里是显示在两列图片的wxml代码,我们可以看到在scroll-view>组件上,我们通过使用bindscrolltolower设置了事件监听函数,当滚动到底部的时候,会触发loadImages去再加载下一组的图片数据,这样就形成了无限的加载:/scroll-view> [代码]<scroll-view scroll-y="true" style="height:{{scrollH}}px" bindscrolltolower="loadImages"> <view style="width:100%"> <view class="img_item"> <view wx:for="{{col1}}" wx:key="id"> <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px">image> view> view> <view class="img_item"> <view wx:for="{{col2}}" wx:key="id"> <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px">image> view> view> view>scroll-view>[代码]好了,挺简单的一个例子,如果你有更好的方法,不吝分享一下哦。 完整代码可以在我的Github下载:https://github.com/zarknight/wx-falls-layout 原作者:一斤代码(简书作者) 原文链接:http://www.jianshu.com/p/260f2623562d
2016-11-29 - 【改进版】如何从零实现上拉无限加载瀑布流组件
前言 之前分享过一篇瀑布流如何实现的文章,经过时间的证明,之前的做法并不好,性能上会有问题,所以还是不投机取巧了,老老实实的实现。 回顾: 通过grid-auto-rows的特性实现 item通过grid-row设置高度 js获取节点高度计算span的值 通过wxs设置css的变量实现修改样式 痛点: grid-auto-rows数值越大,span计算准确度越低。 谷歌浏览器、微信开发工具,如果界面高度超过[代码]1000 * grid-auto-rows[代码]的高度,那么后面的内容就不会显示了,谷歌解释说是为了不过渡消耗性能。 因为性能问题,超过1000的item就不会显示了,全会挤压在最下面,导致页面非常卡,开发工具能直接卡崩溃,手机上还没发现这个问题,之前也忽视了这个问题,后面调试的时候就非常恼火,开发工具跟真机上效果不一致。 为了保证span计算的准确度高,grid-auto-rows一般设置成1-10px,1px准确就等于view的高度,但是超过1000px就卡没了。 实现思路 通过selectAllComponents获取所有的子节点 通过getComputedStyle获取节点的高度 简单的排序算法计算节点位置 设置节点的样式 通过wxs的[代码]getState[代码]储存每屏节点渲染的数据 触发[代码]image[代码]组件的[代码]load[代码]事件重新计算并渲染节点位置 创建组件 需要开启抽象节点 [代码]// waterfall/index.json { "componentGenerics": { "selectable": true } } [代码] 利用wxs响应事件获取页面的节点 [代码]<view class="waterfall" views="{{ views.length }}" data-option="{{ {span} }}" change:views="{{ wxs.init }}" > <!-- 嵌套遍历views二维数组 --> <block wx:for="{{ views }}" wx:key="item" wx:for-index="i" > <selectable class="item view-{{ i }}" wx:for="{{ item }}" wx:key="item" value="{{ item }}" /> </block> </view> [代码] 创建item的x,y边距变量 [代码]--span[代码] [代码].waterfall { --span: 5px; width: 100%; position: relative; .item { width: calc(50% - var(--span)); position: absolute; } } [代码] 创建 [代码]index.wxs[代码],核心业务代码都写在这里 [代码]// 当views被setData的时候会被触发 module.export = { init: function(newValue, oldValue, ownerInstance, instance) { console.log(newValue, oldValue, ownerInstance, instance) } } [代码] 业务逻辑 步骤一:获取所有节点 [代码]function init(length, oldValue, ownerInstance, instance) { // 加个判断,避免views长度为0时,或者长度为发生变化时也会执行业务代码 // 只有当views被push新的内容才会执行下面的业务 if (!length || length === oldValue) return // index 其实就是views的长度减一,就等于当前的数组下标 var index = length - 1 var views = ownerInstance.selectAllComponents('.view-' + index) console.log(JSON.stringify(views)) } [代码] [图片] [图片] 步骤二:遍历views获取节点的高度 [代码]views.forEach(function(v, k){ var viewStyle = v.getComputedStyle(['width', 'height']) // 获取高度 var height = viewStyle.height console.log(viewStyle) // [WXS Runtime info] {"width":"182.5px", "height":"242px"} }) [代码] 步骤三:计算view的位置信息 [代码]var LH = 0 var RH = 0 views.forEach(function (v, k) { var viewStyle = v.getComputedStyle(['width', 'height']) // 格式化高度,将px去掉 var height = fixUnit(viewStyle.height) var style = {} if (LH <= RH) { style = { left: 0, top: LH + 'px' } LH += height } else if (RH < LH) { style = { right: 0, top: RH + 'px' } RH += height } // 设置view的样式 v.setStyle(style) }) [代码] 此时,页面的节点会根据position自动排列好 [图片] 步骤四:储存LH,RH到局部变量 [代码]function init(length, oldValue, ownerInstance, instance) { if (!length || length === oldValue) return // 获取局部变量 var state = ownerInstance.getState() // 获取当前节点的dataset var dataset = instance.getDataset() var index = length - 1 state.option = dataset.option state.page = length // 创建并生成记录左侧、右侧高度 // 用二维数组来记录 if (!state.heights) { state.heights = [[0, 0]] } // 记录初次渲染时间戳 if (!state.timeouts) { state.timeouts = [] } // 获取时间戳,并且加上3000毫秒,用于后面计算图片loaded完是否超时 state.timeouts[index] = getDate().getTime() + 3000 refreshViews(index, ownerInstance, state) } function refreshViews(index, ownerInstance, state) { var views = ownerInstance.selectAllComponents('.view-' + index) var span = state.option.span var LH = state.heights[index][0] // 左侧 var RH = state.heights[index][1] // 右侧 views.forEach(function (v, k) { var viewStyle = v.getComputedStyle(['width', 'height']) var height = fixUnit(viewStyle.height) var style = {} if (LH <= RH) { style = { left: 0, top: LH + 'px' } LH += height + span[0] } else if (RH < LH) { style = { right: 0, top: RH + 'px' } RH += height + span[0] } v.setStyle(style) // 保存LH, RH的值到state.heights // 当前的LH,RH其实就是下屏开始的坐标 state.heights[index + 1] = [LH, RH] console.log('渲染', index, k) }) } [代码] 步骤五:图片加载完重新计算位置 [代码]// waterfall/index.js Component({ properties: { views: Array, span: { type: Array, value: [10, 10], }, }, methods: { onLoaded({ detail: { width, height, pIndex, index } }) { this.setData({ [`views[${pIndex}][${index}].loaded`]: { width, height }, }) }, }, }) [代码] [代码]function loaded(value, oldValue, ownerInstance, instance) { if (!value.item.loaded || !oldValue) return // 获取局部变量 var state = ownerInstance.getState() // 判断加载是否超时,如果超时则不触发计算渲染事件 // 让该节点保持当前的位置及高度 var timeout = state.timeouts[value.pIndex] if (timeout < getDate().getTime()) { console.log('加载超时') return } var view = instance.selectComponent('.loaded-view') var viewWidth = view.getComputedStyle(['width']).width // 设置虚拟节点card组件里的loaded-view高度 view.setStyle({ height: computedHeight( viewWidth, value.item.loaded.width, value.item.loaded.height ) + 'px', }) // 加个函数防抖,因为图片加载快的情况下,会并发触发事件 // 尽可能的少触发计算,渲染事件,保证性能 ownerInstance.clearTimeout(timer) timer = ownerInstance.setTimeout(function () { // 渲染当前图片加载完后面的所有views // for循环处理当前图片所在的views,以及后面所有的views // 因为有些图片过大,可能会加载5s左右,但是用户如果上拉又加载了 // 一屏内容并且也通过计算渲染了,这时候上一屏又触发了计算渲染 // 那么可能位置信息就会发生变化,导致被遮挡,或者有空白,这时候只能 // 计算触发事件的图片以及后面的图片,保证位置信息是正确的 for (var i = value.pIndex; i < state.page; i++) { console.log('需要渲染', i) refreshViews(i, ownerInstance, state) } }, 300) } [代码] [图片] 优化 瀑布流最好后台会返回图片的尺寸信息,然后初次渲染的时候就计算好节点的长宽比例,这样就不用监听图片loaded事件了,瀑布流组件代码也不会频繁触发计算渲染,性能也好,方法也简单。 [代码]<image src="xxxx" style="{{ wxs.computed({width, height}) }}" /> [代码] [代码]// wxs function computed(option) { // 节点宽度自己去计算 var viewWidth = 375 / 2 var width = option.width var height = option.height return (viewWidth / width) * height + 'px } [代码] 完整代码 打开代码片段https://developers.weixin.qq.com/s/SO5q6UmF7doL,可直接运行。 https://github.com/liziwork/li-ui github 如果打不开,请切换到码云,gitee.com,代码同步更新的,觉得有用动动您的小手点个Star。 扫码查看更多组件 [图片]
2021-03-19 - 多形态小程序日历组件,轻松搞定项目需求
小程序日历组件 小程序日历组件,支持多种模式,简单易用好上手。 4种日历模式 3种日期选择方式 支持自定义节假日 支持自定义日期内容 懒加载保证渲染性能 支持农历 支持根据指定日期自动生成 支持跨无数据月份 [图片] [图片] [图片] [图片] [图片] 日历组件基础配置 wxml模板 [代码]<ui-calendar dataSource="{{config}}" /> [代码] 配置日历组件 [代码]Pager({ data: { source: { $$id: 'calendar', mode: 1, // 纵向日历 type: 'range', // 区域选择 tap: 'onTap', // page响应事件 total: 365, // 指定日历总天数 data: [], // 按给定日期计算total值,自动构建日历 rangeCount: 28, // 区选区间28天 rangeMode: 2, // 区选模式 rangeTip: ['入住', '离店'], // 区选提示 festival: true, // 开启节假日显示 alignMonth: false, // 月份对齐,swiper切换时 lunar: false, // 是否显示农历 date: [], // 指定日期显示的内容 value: ['2019-12-24', '2020-01-05'], // 默认值 toolbox: { monthHeader: true, // 是否显示月头 discontinue: false, // 自动构建时,是否省略无数据的月份 }, methods: { // 响应 tap事件 onTap(e, param, inst) { // param.date 选中的当前日期 // 当区选模式时 // param.range === 'start' 区选第一天 // param.range === 'end' 区选最后一天 } } } } }) [代码] github地址:https://github.com/webkixi/aotoo-xquery 小程序demo演示 [图片]
2020-06-30 - 【开箱即用】分享几个好看的波浪动画css效果!
以下代码不一定都是本人原创,很多都是借鉴参考的(模仿是第一生产力嘛),有些已忘记出处了。以下分享给大家,供学习参考!欢迎收藏补充,说不定哪天你就用上了! 一、第一种效果 [图片] [代码]//index.wxml <view class="zr"> <view class='user_box'> <view class='userInfo'> <open-data type="userAvatarUrl"></open-data> </view> <view class='userInfo_name'> <open-data type="userNickName"></open-data> , 欢迎您 </view> </view> <view class="water"> <view class="water-c"> <view class="water-1"> </view> <view class="water-2"> </view> </view> </view> </view> //index.wxss .zr { color: white; background: #4cb4e7; /*#0396FF*/ width: 100%; height: 100px; position: relative; } .water { position: absolute; left: 0; bottom: -10px; height: 30px; width: 100%; z-index: 1; } .water-c { position: relative; } .water-1 { background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0xPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMTMzLjAwMDAwMCkiIGZpbGwtb3BhY2l0eT0iMC4zIiBmaWxsPSIjRkZGRkZGIj4KICAgICAgICAgICAgPGcgaWQ9IndhdGVyLTEiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMS4wMDAwMDAsIDEzMy4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLDcuNjk4NTczOTUgTDQuNjcwNzE5NjJlLTE1LDYwIEw2MDAsNjAgTDYwMCw3LjM1MjMwNDYxIEM2MDAsNy4zNTIzMDQ2MSA0MzIuNzIxMDUyLDI0LjEwNjUxMzggMjkwLjQ4NDA0LDcuMzU2NzQxODcgQzE0OC4yNDcwMjcsLTkuMzkzMDMwMDggMCw3LjY5ODU3Mzk1IDAsNy42OTg1NzM5NSBaIiBpZD0iUGF0aC0xIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-1 3.5s infinite linear; animation: wave-animation-1 3.5s infinite linear; } .water-2 { top: 5px; background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjYwMHB4IiBoZWlnaHQ9IjYwcHgiIHZpZXdCb3g9IjAgMCA2MDAgNjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCAzLjQgKDE1NTc1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT53YXRlci0yPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+PC9kZWZzPgogICAgPGcgaWQ9IuaIkSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9Ii0iIHNrZXRjaDp0eXBlPSJNU0FydGJvYXJkR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjEuMDAwMDAwLCAtMjQ2LjAwMDAwMCkiIGZpbGw9IiNGRkZGRkYiPgogICAgICAgICAgICA8ZyBpZD0id2F0ZXItMiIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIxLjAwMDAwMCwgMjQ2LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTAsNy42OTg1NzM5NSBMNC42NzA3MTk2MmUtMTUsNjAgTDYwMCw2MCBMNjAwLDcuMzUyMzA0NjEgQzYwMCw3LjM1MjMwNDYxIDQzMi43MjEwNTIsMjQuMTA2NTEzOCAyOTAuNDg0MDQsNy4zNTY3NDE4NyBDMTQ4LjI0NzAyNywtOS4zOTMwMzAwOCAwLDcuNjk4NTczOTUgMCw3LjY5ODU3Mzk1IFoiIGlkPSJQYXRoLTIiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMC4wMDAwMDAsIDMwLjAwMDAwMCkgc2NhbGUoLTEsIDEpIHRyYW5zbGF0ZSgtMzAwLjAwMDAwMCwgLTMwLjAwMDAwMCkgIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==") repeat-x; background-size: 600px; -webkit-animation: wave-animation-2 6s infinite linear; animation: wave-animation-2 6s infinite linear; } .water-1, .water-2 { position: absolute; width: 100%; height: 60px; } .back-white { background: #fff; } @keyframes wave-animation-1 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } @keyframes wave-animation-2 { 0% { background-position: 0 top; } 100% { background-position: 600px top; } } .user_box { display: flex; z-index: 10000 !important; opacity: 0; /* 透明度*/ animation: love 1.5s ease-in-out; animation-fill-mode: forwards; } .userInfo_name { flex: 1; vertical-align: middle; width: 100%; margin-left: 5%; margin-top: 5%; font-size: 42rpx; } .userInfo { flex: 1; width: 100%; border-radius: 50%; overflow: hidden; max-height: 50px; max-width: 50px; margin-left: 5%; margin-top: 5%; border: 2px solid #fff; } [代码] 二、第二种效果 [图片] [代码]//index.wxml <view class="waveWrapper waveAnimation"> <view class="waveWrapperInner bgTop"> <view class="wave waveTop" style="background-image: url('https://s2.ax1x.com/2019/09/26/um8g7n.png')"></view> </view> <view class="waveWrapperInner bgMiddle"> <view class="wave waveMiddle" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGZ38.png')"></view> </view> <view class="waveWrapperInner bgBottom"> <view class="wave waveBottom" style="background-image: url('https://s2.ax1x.com/2019/09/26/umGuuQ.png')"></view> </view> </view> //index.wxss .waveWrapper { overflow: hidden; position: absolute; left: 0; right: 0; height: 300px; top: 0; margin: auto; } .waveWrapperInner { position: absolute; width: 100%; overflow: hidden; height: 100%; bottom: -1px; background-image: linear-gradient(to top, #86377b 20%, #27273c 80%); } .bgTop { z-index: 15; opacity: 0.5; } .bgMiddle { z-index: 10; opacity: 0.75; } .bgBottom { z-index: 5; } .wave { position: absolute; left: 0; width: 500%; height: 100%; background-repeat: repeat no-repeat; background-position: 0 bottom; transform-origin: center bottom; } .waveTop { background-size: 50% 100px; } .waveAnimation .waveTop { animation: move-wave 3s; -webkit-animation: move-wave 3s; -webkit-animation-delay: 1s; animation-delay: 1s; } .waveMiddle { background-size: 50% 120px; } .waveAnimation .waveMiddle { animation: move_wave 10s linear infinite; } .waveBottom { background-size: 50% 100px; } .waveAnimation .waveBottom { animation: move_wave 15s linear infinite; } @keyframes move_wave { 0% { transform: translateX(0) translateZ(0) scaleY(1) } 50% { transform: translateX(-25%) translateZ(0) scaleY(0.55) } 100% { transform: translateX(-50%) translateZ(0) scaleY(1) } } [代码] 三、第三种效果 [图片] [代码]//index.wxml <view class="container"> <image class="title" src="https://ftp.bmp.ovh/imgs/2019/09/74bada9c4143786a.png"></image> <view class="content"> <view class="hd" style="transform:rotateZ({{angle}}deg);"> <image class="logo" src="https://ftp.bmp.ovh/imgs/2019/09/d31b8fcf19ee48dc.png"></image> <image class="wave" src="wave.png" mode="aspectFill"></image> <image class="wave wave-bg" src="wave.png" mode="aspectFill"></image> </view> <view class="bd" style="height: 100rpx;"> </view> </view> </view> //index.wxss image{ max-width:none; } .container { background: #7acfa6; align-items: stretch; padding: 0; height: 100%; overflow: hidden; } .content{ flex: 1; display: flex; position: relative; z-index: 10; flex-direction: column; align-items: stretch; justify-content: center; width: 100%; height: 100%; padding-bottom: 450rpx; background: -webkit-gradient(linear, left top, left bottom, from(rgba(244,244,244,0)), color-stop(0.1, #f4f4f4), to(#f4f4f4)); opacity: 0; transform: translate3d(0,100%,0); animation: rise 3s cubic-bezier(0.19, 1, 0.22, 1) .25s forwards; } @keyframes rise{ 0% {opacity: 0;transform: translate3d(0,100%,0);} 50% {opacity: 1;} 100% {opacity: 1;transform: translate3d(0,450rpx,0);} } .title{ position: absolute; top: 30rpx; left: 50%; width: 600rpx; height: 200rpx; margin-left: -300rpx; opacity: 0; animation: show 2.5s cubic-bezier(0.19, 1, 0.22, 1) .5s forwards; } @keyframes show{ 0% {opacity: 0;} 100% {opacity: .95;} } .hd { position: absolute; top: 0; left: 50%; width: 1000rpx; margin-left: -500rpx; height: 200rpx; transition: all .35s ease; } .logo { position: absolute; z-index: 2; left: 50%; bottom: 200rpx; width: 160rpx; height: 160rpx; margin-left: -80rpx; border-radius: 160rpx; animation: sway 10s ease-in-out infinite; opacity: .95; } @keyframes sway{ 0% {transform: translate3d(0,20rpx,0) rotate(-15deg); } 17% {transform: translate3d(0,0rpx,0) rotate(25deg); } 34% {transform: translate3d(0,-20rpx,0) rotate(-20deg); } 50% {transform: translate3d(0,-10rpx,0) rotate(15deg); } 67% {transform: translate3d(0,10rpx,0) rotate(-25deg); } 84% {transform: translate3d(0,15rpx,0) rotate(15deg); } 100% {transform: translate3d(0,20rpx,0) rotate(-15deg); } } .wave { position: absolute; z-index: 3; right: 0; bottom: 0; opacity: 0.725; height: 260rpx; width: 2250rpx; animation: wave 10s linear infinite; } .wave-bg { z-index: 1; animation: wave-bg 10.25s linear infinite; } @keyframes wave{ from {transform: translate3d(125rpx,0,0);} to {transform: translate3d(1125rpx,0,0);} } @keyframes wave-bg{ from {transform: translate3d(375rpx,0,0);} to {transform: translate3d(1375rpx,0,0);} } .bd { position: relative; flex: 1; display: flex; flex-direction: column; align-items: stretch; animation: bd-rise 2s cubic-bezier(0.23,1,0.32,1) .75s forwards; opacity: 0; } @keyframes bd-rise{ from {opacity: 0; transform: translate3d(0,60rpx,0); } to {opacity: 1; transform: translate3d(0,0,0); } } [代码] wave.png(可下载到本地) [图片] 在这个基础上,再加上js的代码,即可实现根据手机倾向,水波晃动的效果 wx.onAccelerometerChange(function callback) 监听加速度数据事件。 [图片] [代码]//index.js Page({ onReady: function () { var _this = this; wx.onAccelerometerChange(function (res) { var angle = -(res.x * 30).toFixed(1); if (angle > 14) { angle = 14; } else if (angle < -14) { angle = -14; } if (_this.data.angle !== angle) { _this.setData({ angle: angle }); } }); }, }); [代码] 四、第四种效果 [图片] [代码]//index.wxml <view class='page__bd'> <view class="bg-img padding-tb-xl" style="background-image:url('http://wx4.sinaimg.cn/mw690/006UdlVNgy1g2v2t1ih8jj31hc0p0qej.jpg');background-size:cover;"> <view class="cu-bar"> <view class="content text-bold text-white"> 悦拍屋 </view> </view> </view> <view class="shadow-blur"> <image src="https://raw.githubusercontent.com/weilanwl/ColorUI/master/demo/images/wave.gif" mode="scaleToFill" class="gif-black response" style="height:100rpx;margin-top:-100rpx;"></image> </view> </view> //index.wxss @import "colorui.wxss"; .gif-black { display: block; border: none; mix-blend-mode: screen; } [代码] 本效果需要引入ColorUI组件库
2019-09-26 - 教你怎么监听小程序的返回键
更新:2020年7月28日08:51:11 基础库2.12.0起,可以调用wx.enableAlertBeforeUnload监听原生右上角返回、物理返回以及wx.navigateBack时弹框提示 AIP详情请看: https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.enableAlertBeforeUnload.html //======================================== 怎么监听小程序的返回键? 应该有很多人想要监听用户的这个动作吧,但是很遗憾,小程序不会给你这个API的,那是不是就没辙了? 幸好我们还可以自定义导航栏,这样一来我们就可以监听用户的这一动作了。 什么?这你已经知道啦? 那好咱们就不说自定义导航栏的返回监听了,说一下物理返回和左滑?右滑?(不管了,反正是滑)返回上一页怎么监听。 监听物理返回 首先说一下这个监听方法的缺点,虽说是监听,但是还是无法真正意义上的监听并拦截来阻止页面跳转,页面还是会返回上一页,而后重新载入刚刚的页面,如果这不是你想要的,那可以不用往下看了 其次说一下用到什么东西: wx.onAppRoute、wx.showModal 最后是一些主要代码: 重写wx.showModal,主要是加个confirmStay参数和使wx.showModal Promise化 [代码]const { showModal } = wx; Object.defineProperty(wx, 'showModal', { configurable: false, // 是否可以配置 enumerable: false, // 是否可迭代 writable: false, // 是否可重写 value(...param) { return new Promise(function (rs, rj) { let { success, fail, complete, confirmStay } = param[0] param[0].success = (res) => { res.navBack = (res.confirm && !confirmStay) || (res.cancel && confirmStay) wx.setStorageSync('showBackModal', !res.navBack) success && success(res) rs(res) } param[0].fail = (res) => { fail && fail(res) rj(res) } param[0].complete = (res) => { complete && complete(res) (res.confirm || res.cancel) ? rs(res) : rj(res) } return showModal.apply(this, param); // 原样移交函数参数和this }.bind(this)) } }); [代码] 使用wx.onAppRoute实现返回原来的页面 [代码]wx.onAppRoute(function (res) { var a = getApp(), ps = getCurrentPages(), t = ps[ps.length - 1], b = a && a.globalData && a.globalData.pageBeforeBacks || {}, c = a && a.globalData && a.globalData.lastPage || {} if (res.openType == 'navigateBack') { var showBackModal = wx.getStorageSync('showBackModal') if (c.route && showBackModal && typeof b[c.route] == 'function') { wx.navigateTo({ url: '/' + c.route + '?useCache=1', }) b[c.route]().then(res => { if (res.navBack){ a.globalData.pageBeforeBacks = {} wx.navigateBack({ delta: 1 }) } }) } } else if (res.openType == 'navigateTo' || res.openType == 'redirectTo') { if (!a.hasOwnProperty('globalData')) a.globalData = {} if (!a.globalData.hasOwnProperty('lastPage')) a.globalData.lastPage = {} if (!a.globalData.hasOwnProperty('pageBeforeBacks')) a.globalData.pageBeforeBacks = {} if (ps.length >= 2 && t.onBeforeBack && typeof t.onBeforeBack == 'function') { let { onUnload } = t wx.setStorageSync('showBackModal', !0) t.onUnload = function () { a.globalData.lastPage = { route: t.route, data: t.data } onUnload() } } t.onBeforeBack && typeof t.onBeforeBack == 'function' && (a.globalData.pageBeforeBacks[t.route] = t.onBeforeBack) } }) [代码] 改造Page [代码]const myPage = Page Page = function(e){ let { onLoad, onShow, onUnload } = e e.onLoad = (() => { return function (res) { this.app = getApp() this.app.globalData = this.app.globalData || {} let reinit = () => { if (this.app.globalData.lastPage && this.app.globalData.lastPage.route == this.route) { this.app.globalData.lastPage.data && this.setData(this.app.globalData.lastPage.data) Object.assign(this, this.app.globalData.lastPage.syncProps || {}) } } this.useCache = res.useCache res.useCache ? reinit() : (onLoad && onLoad.call(this, res)) } })() e.onShow = (() => { return function (res) { !this.useCache && onShow && onShow.call(this, res) } })() e.onUnload = (() => { return function (res) { this.app.globalData = Object.assign(this.app.globalData || {}, { lastPage: this }) onUnload && onUnload.call(this, res) } })() return myPage.call(this, e) } [代码] 在需要监听的页面加个onBeforeBack方法,方法返回Promise化的wx.showModal [代码]onBeforeBack: function () { return wx.showModal({ title: '提示', content: '信息尚未保存,确定要返回吗?', confirmStay: !1 //结合content意思,点击确定按钮,是否留在原来页面,confirmStay默认false }) } [代码] 运行测试,Oj8K 是不是很简单,马上去试试水吧,效果图就不放了,静态图也看不出效果,动态图懒得弄,想看效果的自己运行代码片段吧 代码片段 https://developers.weixin.qq.com/s/hc2tyrmw79hg
2020-07-28