- 【好文】小程序动态换肤解决方案 - 终极篇
小程序动态换肤解决方案 – 终极篇 回顾 早些日子,我写过两篇文章介绍过在微信小程序内,如何实现换肤功能,下面贴出链接,没看过的同学可以先看看 小程序动态换肤解决方案 – 本地篇 小程序动态换肤解决方案 – 接口篇 但是上面两种方案都有不足之处,所以我在文末也备注了会出 终极篇解决方案,拖延了一些时间,今天看到评论区有人cue我说什么时候出终极篇,于是,今天花了写时间整理了一下,希望可以帮助到大家。 方案 其实这篇文章提供的解决方案,更多是 [代码]接口篇[代码]的优化版本。 解决思路就是: 将接口获取到的皮肤色值属性,动态设置到需要换肤的元素的某个属性上,本质上就是替换元素的css属性的属性值,方法就是通过给当前[代码]Page[代码]和[代码]Component[代码]对象的[代码]js文件[代码]嵌入提前设置好的[代码]css变量[代码]中,然后通过[代码]setData[代码]的方法回显到对应的[代码]wxml文件[代码]中。 采用 css变量 的方式替代原有 内联修改样式 的方式; 采用小程序原生提供的mixin解决方案 —— [代码]Behavior[代码],对页面还有组件对象来说,虽有一定的侵害性,但是可以极大程度的降低重复代码的编写; 代码 1. 监听器模块 我们知道,接口返回的数据是异步的,所以,当我们进入到指定的 [代码]Page[代码]和[代码]Component[代码] 对象内部的时候,有可能还没得到数据,就需要先注册一个监听函数,等到皮肤接口请求成功之后,然后再执行皮肤设值操作; [代码]// observer.js function Observer() { this.actions = new Map() } // 监听事件 Observer.prototype.addNotice = function(key, action) { // 因为同个Page(页面)或者Component(组件)对象有可能引入多个组件 // 这些组件都用到了同一个监听器,每个监听器的回调函数需要单独处理 // 因此,结果就是: key => [handler1, hander2, hander3....] if (this.actions.has(key)) { const handlers = this.actions.get(key) this.actions.set(key, [...handlers, action]) } else { this.actions.set(key, [action]) } } // 删除监听事件 Observer.prototype.removeNotice = function(key) { this.actions.delete(key) } // 发送事件 Observer.prototype.postNotice = function(key, params) { if (this.actions.has(key)) { const handlers = this.actions.get(key) // 皮肤接口获取数据成功,取出监听器处理函数,依次执行 handlers.forEach(handler => handler(params)) } } module.exports = new Observer() [代码] 2. 皮肤对象模型模块 因为皮肤接口只会在程序首次加载运行的时候执行,换言之,通过 [代码]发布-订阅[代码] 的方式来设置皮肤只会[代码]发生在第一次接口请求成功之后[代码],后期都不会再执行;因此,我们需要通过一个Model模型对象将数据存储起来,后面的皮肤设值操作都从该model对象中获取; [代码]// viModel.js /** * @param {*} mainColor 主色值 * @param {*} subColor 辅色值 * @param {*} reset 重置 */ function ViModel(mainColor, subColor, reset = false) { // 如果当前实例已经设置过,直接返回该实例 if (typeof ViModel.instance == 'object' && !reset) { return ViModel.instance } this.mainColor = mainColor this.subColor = subColor // 实例赋值动作触发在接口有数据返回的时候 if (this.mainColor || this.subColor) { ViModel.instance = this } return this } module.exports = { // 通过save方法来赋值要通过reset = true来重置对象 save: function(mainColor = '', subColor = '') { return new ViModel(mainColor, subColor, true) }, // 直接返回的都是已经有值的单例实例 get: function() { return new ViModel() } } [代码] 3. 小程序Mixin模块 —— Behavior 这个就是这次分享的最为重要的模块 —— 注入 themeStyle 的css变量 我们直接来看这段代码: [代码]setThemeStyle({ mainColor, subColor }) { this.setData({ themeStyle: ` --main-color: ${mainColor}; --sub-color: ${subColor}; ` }) } [代码] 想必看到这里,大家应该猜到开篇说的实现原理了 这里的 [代码]themeStyle[代码] 就是我们接下来要注入到 [代码]Page[代码] 和 [代码]Component[代码] 的 data 属性,也就是需要在页面和组件中设置的[代码]动态css变量属性[代码] [代码]//skinBehavior.js const observer = require('./observer'); const viModel = require('./viModel'); module.exports = Behavior({ data: { themeStyle: null }, attached() { // 1. 如果接口响应过长,创建监听,回调函数中读取结果进行换肤 observer.addNotice('kNoticeVi', function(res) { this.setThemeStyle(res) }.bind(this)) // 2. 如果接口响应较快,modal有值,直接赋值,进行换肤 const themeData = viModel.get() if (themeData.mainColor || themeData.subColor) { this.setThemeStyle(themeData) } }, detached() { observer.removeNotice('kNoticeVi') }, methods: { setThemeStyle({ mainColor, subColor }) { this.setData({ themeStyle: ` --main-color: ${mainColor}; --sub-color: ${subColor}; ` }) }, }, }) [代码] 4. 【应用】—— Component模块 js 文件引入[代码]skinBehavior.js[代码],通过[代码]Component对象[代码]提供的[代码]behaviors[代码]属性注入进去; wxml 文件根节点设置[代码]style="{{themeStyle}}"[代码],设置css变量值; wxss 文件通过css变量设置皮肤色值 [代码]background: var(--main-color, #0366d6);[代码] [代码]// wxButton2.js const skinBehavior = require('../../js/skinBehavior'); Component({ behaviors: [skinBehavior], properties: { // 按钮文本 btnText: { type: String, value: '' }, // 是否为辅助按钮,更换辅色皮肤 secondary: { type: Boolean, value: false } } }) [代码] [代码]<!-- wxButton2.wxml --> <view class="btn-default btn {{secondary ? 'btn-secondary' : ''}}" style="{{themeStyle}}">{{ btnText }}</view> [代码] [代码]/* wxButton2.wxss */ .btn { width: 200px; height: 44px; line-height: 44px; text-align: center; color: #fff; } .btn.btn-default { background: var(--main-color, #0366d6); } .btn.btn-secondary { background: var(--sub-color, #0366d6); } [代码] 5. 【应用】 —— Page模块 使用方法跟Component模块一样,就不写了,下面贴一下代码: [代码]// skin.js const skinBehavior = require('../../js/skinBehavior'); Page({ behaviors: [skinBehavior], onLoad() { console.log(this.data) } }) [代码] [代码]<!--skin.wxml--> <view class="page" style="{{themeStyle}}"> 换肤终极篇 <view class="body"> <wxButton2 class="skinBtn" btnText="按钮1"></wxButton2> <wxButton2 class="skinBtn"btnText="按钮2" secondary></wxButton2> <wxButton2 class="skinBtn" btnText="按钮2" ></wxButton2> </view> </view> [代码] [代码]/* skin.wxss */ .page { padding: 20px; color: var(--main-color); } .skinBtn { margin-top: 10px; float: left; } [代码] 6. 【初始化】—— 接口调用 这里就是在小程序的启动文件 app.js 调用皮肤请求接口,初始化皮肤 [代码]// app.js const { getSkinSettings } = require('./js/service'); App({ onLaunch: function () { // 页面启动,请求接口 getSkinSettings().catch(err => { console.log(err) }) } }) [代码] 效果展示 [图片] 项目地址 项目地址:https://github.com/csonchen/wxSkin 这是本文案例的项目地址,如果觉得好,希望大家都去点下star哈,谢谢大家。。。
2021-03-22 - 如何实现加入购物车的抛物线效果
一、场景分析 在一些如商城、点餐小程序中实现购物车抛物线效果可以提升界面趣味性增加小程序用户体验。 二、效果预览 效果图压缩后速度有点快,请下载代码片段预览 [图片] 三、实现原理 当用户点击物品时记录当前触摸点,根据触摸点计算抛物线运动的顶点位置,通过触摸点、顶点、购物车的位置计算出抛物线运动轨迹,然后控制 icon 运动。 计算购物车在当前手机内的位置 [代码]/** 设置购物车的坐标位置 **/ wx.getSystemInfo({ success: (res) => { let busPos = {} // x y 坐标分别取屏幕百分之八十的位置 busPos['x'] = res.windowWidth * 0.8 busPos['y'] = res.windowHeight * 0.8 this.setData({ busPos }) } }) [代码] 商品点击事件的处理 点击物品后记录点击的位置,然后根据点击位置计算出抛物线的顶点位置,计算方式为点击位置的上方+150,右边+150(需要根据点击位置是否在购物左边还是右边进行判断)。 根据点击,顶点,购物车三个位置计算出抛物线运动轨迹 以3个控制点为例,点A、B、C、AB 上设置点 D、BC 上设置点 E、DE 连线上设置点 F,则最终的贝塞尔曲线是点F的坐标轨迹; 计算相邻控制点间距; 根据完成时间,计算每次执行时 D 在AB方向上移动的距离,E 在 BC 方向上移动的距离; 时间每递增 100ms,则 D、E 在指定方向上发生位移,F 在 DE 上的位移则可通过 AD/AB = DF/DE 得出; 根据 DE 的正余弦值和 DE 的值计算出F的坐标。 开启定时器,依次按照贝塞尔曲线位置做动画位移 使用定时器将抛物线运动轨迹做动画位移。 定时器执行完动画后将购物车角标+1 老规矩,结尾放代码片段 https://developers.weixin.qq.com/s/PnYfitmG7Hxv
2022-03-04