- 小程序粘性布局组件实现
一、前言 开发中,我们经常会遇需要让组件在屏幕范围内时,按照正常布局排列,而组件滚出屏幕范围时,让其始终固定在屏幕顶部的情况,也就是常说的粘性布局。今天我们就一起用小程序来实现一个适用于不同场景下的粘性布局组件。 二、demo演示 如图,实现的组件主要适用于以下几种场景: 吸顶页面最上方; 吸顶与页面有固定距离的位置; 在指定容器内吸顶; 嵌套在scroll-view中吸顶。 [图片] 三、代码演示 其中,粘性组件通过<weimob-sticky></weimob-sticky>调用,参数信息用法如下: 参数 说明 类型 默认值 offset-top 吸顶时与顶部的距离,单位px number 0 z-index 吸顶时的 z-index number 99 container 一个函数,返回容器对应的 NodesRef 节点 function - scroll-top 当前滚动区域的滚动位置,非 null 时会禁用页面滚动事件的监听 number - 滚动时触发scroll函数,其中isFixed为是否吸顶,scrollTop为距离顶部的位置。详细代码如下。 3.1 页面代码 3.1.1 基础用法 [代码]<view class="weimob-block"> <view class="weimob-title">基础用法</view> <view class="weimob-body"> <weimob-sticky> <!-- 需要粘性的部分 --> <button class="margin-left-base" size="mini"> 基础用法 </button> </weimob-sticky> </view> </view> [代码] 3.1.2 吸顶距离 [代码]<view class="weimob-block"> <view class="weimob-title">吸顶距离</view> <view class="weimob-body"> <!-- 吸顶时与顶部的距离,单位px --> <weimob-sticky offset-top="{{ 50 }}"> <!-- 需要粘性的部分 --> <button class="margin-left-top" type="primary" size="mini"> 吸顶距离 </button> </weimob-sticky> </view> </view> [代码] 3.1.3 指定容器 [代码]<view class="weimob-block"> <view class="weimob-title">指定容器</view> <view class="weimob-body"> <!-- 这里需要固定高度 --> <view id="container" style="height: 300rpx;background-color: #fff"> <weimob-sticky container="{{ container }}"> <button size="mini" class="margin-left-special"> 指定容器 </button> </weimob-sticky> </view> </view> </view> [代码] 3.1.4 嵌套在scroll-view使用 [代码]<view class="weimob-block"> <view class="weimob-title">嵌套在 scroll-view 内使用</view> <!-- 这里需要固定高度,scroll-view里的元素高度需要大于其高度 --> <scroll-view bind:scroll="onScroll" scroll-y id="scroller" style="height: 400rpx; background-color: #fff;margin-top: 40rpx;" > <view style="height: 800rpx"> <weimob-sticky scroll-top="{{ scrollTop }}" offset-top="{{ offsetTop }}" > <button size="mini" class="margin-left-scoll"> 嵌套在 scroll-view 内 </button> </weimob-sticky> </view> </scroll-view> </view> [代码] 页面js [代码]Page({ data: { container: null, //一个函数,返回容器对应的 NodesRef 节点 scrollTop: 60, // 当前滚动区域的滚动位置,非null时会禁用页面滚动事件的监听 offsetTop: 0 // 吸顶时与顶部的距离,单位px }, onReady() { // 页面渲染完,获取节点信息 this.setData({ container: () => wx.createSelectorQuery().select('#container'), }); }, onScroll(event) { // 容器滚动时获取节点信息 wx.createSelectorQuery() .select('#scroller') .boundingClientRect((res) => { this.setData({ scrollTop: event.detail.scrollTop, offsetTop: res.top, }); }) .exec(); } }); [代码] 3.2 组件代码 组件wxml [代码]<wxs src="./index.wxs" module="computed" /> <view class="weimob-sticky" style="{{ computed.containerStyle({ fixed, height, zIndex }) }}" > <view class="{{ fixed ? 'weimob-sticky-wrap--fixed' : ''}}" style="{{ computed.wrapStyle({ fixed, offsetTop, transform, zIndex }) }}" > <slot /> </view> </view> [代码] 组件wxs 这里使用使用小程序的wxs对吸顶元素的transform,top,height,z-index元素进行实时渲染,ios设备在滚动监听时性能会优于在js 2-20倍,androd设备效率暂无差异。 [代码]function wrapStyle(data) { var style = ""; if (data.transform) { style += 'transform: translate3d(0, ' + data.transform + 'px, 0);' } if (data.fixed) { style += 'top: ' + data.offsetTop + 'px;' } if (data.zIndex) { style += 'z-index: ' + data.zIndex + ';' } return style; } function containerStyle(data) { var style = ""; if (data.fixed) { style += 'height: ' + data.height + 'px;' } if (data.zIndex) { style += 'z-index: ' + data.zIndex + ';' } return style; } module.exports = { wrapStyle: wrapStyle, containerStyle: containerStyle } [代码] 组件js [代码]import pageScrollMixin from "./page-scroll"; const ROOT_ELEMENT = ".weimob-sticky"; Component({ options: { multipleSlots: true }, properties: { zIndex: { type: Number, value: 99 }, offsetTop: { type: Number, value: 0, observer: "onScroll" }, disabled: { type: Boolean, observer: "onScroll" }, container: { type: null, observer: "onScroll" }, scrollTop: { type: null, observer(val) { this.onScroll({ scrollTop: val }); } } }, data: { height: 0, fixed: false, transform: 0 }, behaviors: [pageScrollMixin(function pageScrollMixinCallback(event) { // 非null时会禁用页面滚动事件的监听 if (this.data.scrollTop != null) { return; } this.onScroll(event); })], lifetimes: { attached() { this.onScroll(); } }, methods: { onScroll({ scrollTop } = {}) { const { container, offsetTop, disabled } = this.data; if (disabled) { this.setDataAfterDiff({ fixed: false, transform: 0 }); return; } this.scrollTop = scrollTop || this.scrollTop; if (typeof container === "function") { // 情况一:指定容器下时,吸顶距离+吸顶元素高度>容器高度+容器距顶部距离,随页面滚动; // 情况二:指定容器下时,吸顶距离>吸顶元素高度,元素固定; // 情况三:元素初始化。 // this.getRect获取节点ROOT_ELEMENT相对于显示区域的top,height等信息,通过root获取 // this.getContainerRect获取父容器相对于显示区域的top,height等信息,通过container获取 Promise.all([this.getRect(ROOT_ELEMENT), this.getContainerRect()]).then( ([root, container]) => { if (offsetTop + root.height > container.height + container.top) { this.setDataAfterDiff({ fixed: false, transform: container.height - root.height }); } else if (offsetTop >= root.top) { this.setDataAfterDiff({ fixed: true, height: root.height, transform: 0 }); } else { this.setDataAfterDiff({ fixed: false, transform: 0 }); } }); return; }else{ this.getRect(ROOT_ELEMENT).then(root => { // 吸顶时与顶部的距离小于可视区域的top距离时,随着滚动条滚动,否则吸顶 if (offsetTop >= root.top) { this.setDataAfterDiff({ fixed: true, height: root.height }); this.transform = 0; } else { this.setDataAfterDiff({ fixed: false }); } return Promise.resolve(); }); } }, setDataAfterDiff(data) { // 比较数据是否与上次相同,不同则触发父组件scroll事件更新isFixed,scrollTop。 wx.nextTick(() => { const diff = Object.keys(data).reduce((prev, key) => { const prevCopy = prev; if (data[key] !== this.data[key]) { prevCopy[key] = data[key]; } return prevCopy; }, {}); this.setData(diff); this.triggerEvent("scroll", { scrollTop: this.scrollTop, isFixed: data.fixed || this.data.fixed }); }); }, getContainerRect() { const nodesRef = this.data.container(); return new Promise(resolve => nodesRef.boundingClientRect(resolve).exec()); }, getRect(selector) { return new Promise(resolve => { wx.createSelectorQuery().in(this).select(selector).boundingClientRect(rect => { resolve(rect); }).exec(); }); } } }); [代码] page-scroll.js 滚动事件在页面进入和离开时共享的pageScrollMixin函数。 [代码]function getCurrentPage() { const pages = getCurrentPages(); return pages[pages.length - 1] || {}; } function onPageScroll(event) { const { weimobPageScroller = [] } = getCurrentPage(); weimobPageScroller.forEach(scroller => { if (typeof scroller === "function" && event) { // @ts-ignore scroller(event); } }); } const pageScrollMixin = scroller => Behavior({ attached() { const page = getCurrentPage(); if (Array.isArray(page.weimobPageScroller)) { page.weimobPageScroller.push(scroller.bind(this)); } else { page.weimobPageScroller = typeof page.onPageScroll === "function" ? [page.onPageScroll.bind(page), scroller.bind(this)] : [scroller.bind(this)]; } page.onPageScroll = onPageScroll; }, detached() { const page = getCurrentPage(); page.weimobPageScroller = (page.weimobPageScroller || []).filter(item => item !== scroller); } }); export default pageScrollMixin; [代码] 总结 最后,我将上述代码放在了代码片段中供大家使用了解,https://developers.weixin.qq.com/s/qiym3wmr7znx ,希望能够帮到小伙伴们,欢迎评论区建议或指教哦~
2021-01-26 - 小程序毫秒级计时优化
需求:在进行复杂程序操作的同时,毫秒级计时。 方案流程: 1. 使用setData进行毫秒级计时,以10s为例,代码很简单: [图片] Ok,搞定了,测试一下,ios正常,android性能欠佳的机型、在没有执行复杂运算的情况居然差了几秒。。。?(有的甚至更多) 我们再稍微加点东西: [图片] 结果:10s的计时跑到了20多秒 原因:主线程中有其他任务在执行导致setInterval延迟执行。 2. 既然这样,我们减去延迟带来的时间差 [图片] 结果:达到效果,只存在了毫秒级的误差了。 完美解决?我们来搞点事情 [图片] 页面渲染出现了卡顿,会看到计时不会连续的变化,出现跳跃减少。 原因分析:数据的setData通信,造成的延迟。 3. 既然setData会有延迟,那么还有什么办法可以更新页面?——canvas 马上动手写个: [图片] 完美决绝!!,requestAnimationFrame小程序之前不支持,若支持可替代,实现方案有多种。 效果对比: 普通方式:http://video.static.logan4846.com/public/1.mp4 canvas:http://video.static.logan4846.com/public/2.mp4 代码片段: https://developers.weixin.qq.com/s/CV8mctmn7AeE
2020-01-19 - react源码解析--开篇
引子:react开源很久了,使用它也有5年的时间了,从最开始用的还是0.13.1版本吧,到现在的16.12,版本经过了历次迭代,也变得越来越优秀了,一直没有学习过它的源码,深感惭愧。这次也趁着这次机会吧,一边读一边写,先从react开始,然后是react-dom、react-server、react插件(ReactEventsTap、ReactEventsSwipe、ReactEventsSwipe…) 等等吧,写成一个系列的文章,希望我能写的完,哈哈哈哈😂~ ps:文中若有写的不对的地方,欢迎大家指出来 一、代码 直接GitHub上拉代码吧~https://github.com/facebook/react,我拉的时候react版本是16.12.0,文章也是基于这个版本。 在真正的开始之前吧,先说一下我个人理解的学习过程,我把它分为三个阶段:基础学习、应用实践、原理研究。所谓基础学习是通过其官方文档,学习他的入门、使用方法、API等等;应用实践是指真正使用它去做一些东西,这个过程很重要,通过这个阶段才能了解我们所学的东西能转换成什么样的生产力,也有助于我们的逐步深入;最后一个过程是原理研究,也就是所谓的实现方式的研究,只有经过了这最后一个步骤,才能在我们的个人介绍里写上“精通xxx”了吧😄(个人意见~) 二、项目解析 1、首先,让我们看一下项目的目录解析~ ├── build 构建后的输出目录 │ ├── dist react相关 │ ├── facebook-www 插件相关 │ └── react-native react-native相关 ├── fixtures React 开发的测试用例 │ ├── art │ ├── attribute-behavior │ ├── concurrent │ ├── devtools │ ├── dom │ ├── eslint │ ├── expiration │ ├── fiber-debugger │ ├── fiber-triangle │ ├── fizz-ssr-browser │ ├── flight │ ├── flight-browser │ ├── packaging │ ├── scheduler │ ├── ssr │ └── tracing ├── packages 源码目录 │ ├── babel-plugin-react-jsx 该软件包旨在最终替代当前的@babel/plugin-transform-react-jsx,将JSX转换从目标React.createElement(type, props, children)更改为React.jsx(types, props, key) │ ├── create-subscription 在组件里订阅额外数据的工具 │ ├── dom-event-testing-library dom事件测试库 │ ├── eslint-plugin-react-hooks eslint插件钩子 │ ├── jest-mock-scheduler │ ├── jest-react │ ├── legacy-events 事件系统 │ ├── react react核心包 │ ├── react-art 矢量图形库 │ ├── react-cache 为 React 应用提供缓存 │ ├── react-debug-tools │ ├── react-devtools │ ├── react-devtools-core │ ├── react-devtools-extensions │ ├── react-devtools-inline │ ├── react-devtools-shared │ ├── react-devtools-shell │ ├── react-dom DOM 渲染相关 │ ├── react-flight │ ├── react-flight-dom-webpack │ ├── react-interactions │ ├── react-is React 元素类型相关 │ ├── react-native-renderer react-native 渲染相关 │ ├── react-noop-renderer Fiber 测试相关 │ ├── react-reconciler React 调制器 │ ├── react-refresh │ ├── react-server 服务端渲染 │ ├── react-test-renderer │ ├── scheduler 调度:异步渲染等 │ ├── shared 通用代码 │ └── use-subscription └── scripts 公共的babel、eslintflow和release等相关的文件 ├── babel ├── bench ├── circleci ├── error-codes ├── eslint ├── eslint-rules ├── flow ├── git ├── jest ├── perf-counters ├── prettier ├── print-warnings ├── release ├── rollup ├── shared ├── tasks └── yarn 我们主要解析的就是packages下的相关包了。 2、源码构建 这个我们看一下package.json文件里的代码: { … “devDependencies”: { … }, … “scripts”: { “build”: “node ./scripts/rollup/build.js”,// 构建命令 … } } 打包的文件都在./scripts/rollup下了,build文件有740行,是构建所有项目的命令,核心代码如下~ [图片] 启动打包的方法就是这句了:createBundle(bundle, bundleType)。 打包的项目配置,全部在bundles.js中了 [图片] 构建的过程会循环包配置文件的配置构建出不同用途的包,依次确定文件名称、打包入口文件、fb文件名称替换、确认依赖包、输出包等过程。那么我们看下createBundle方法里的构建的入口的JS文件地址: [图片] 沿着bundle.entry我们发现它的值为react(在scripts/rollup/bundles.js中),require.resolve用于查询文件的完整绝对路径,也就说react对应的真实入口路径是~/work/react/packages/react/index.js(前两级为项目目录),由此不难看出所有源码都在packages中。 3、react入口文件 根据第二步的构建过程我们可以发现,真正的react入口文件为:…/packages/react/src/React.js [图片] 三、react源码文件 React.js文件内容如下~ [图片] 通过这个文件也可以看的出来,其实React相关部分主要就是定义了React对象,他的属性有组件、元素、自元素、上下文、懒加载方法、ref、memo、React Hooks、ReactElementValidator等,至于他们的实现其实主要是在react-dom中,那么我们要解析React的话就从React各个部分的使用方法介绍(着重)以及他们的定义(源码查看)这两个方面来进行吧。 四、小结 本篇文章首先了解了整个项目的结构目录,然后通过查看整个项目的构建文件,研究其构建方式,找到了真正的源码文件入口,也正是开启了react部分的源码的解析系列文章,先预告一下下一篇文章react源码解析–React组件。 欢迎收藏+转发~ ps:再次请求大家如果发现有错误,请帮忙指正~
2020-01-23 - 搭建一个https网站的全过程
概述:本着学习的目的,做了这个分享。自己切切实实的做完了整个流程,发现其中的坑也是蛮多的,当然自己的收获对应也是蛮多的。写下这个流程一方面为了加深自己的印象、可以在将来回顾一下,另一方面也是为了给有需要的人提供帮助~ 一、服务器准备 [代码]为了演示方便,我购买了一台腾讯云服务器 [代码] [图片] 安装的操作系统是centos 二、域名准备 1、域名注册 可以从万网上进行注册:https://wanwang.aliyun.com/ 2、域名备案 备案流程略复杂,这里只列了一个步骤简介 入口:https://beian.aliyun.com/ [图片] 域名的备案时间较长,建议大家提前准备起来。 3、域名解析 域名备案通过之后,为我们的网站准备一个子域名,入口:https://dns.console.aliyun.com/?spm=a2c1d.8251892.aliyun_sidebar.daliyun_sidebar_dns.37575b76kNuXEO#/dns/domainList [图片] 点击上图的解析设置 [图片] 将记录值填写我们刚刚买的服务器的公网ip 三、申请ssl证书 我是从腾讯提供的证书服务里申请的,腾讯申请入口 https://console.cloud.tencent.com/ssl 点击“申请免费证书” [图片] 这里选择了手动Dns验证 [图片] 申请完成后会有个表格,是说明如何Dns校验的,要求域名下添加一条解析记录 [图片] Dns校验 首先,在域名下添加一条txt记录 [图片] 然后,单机自助诊断旁边的“查询” [图片] 下载证书 [图片] 点击图中的下载即可 四、服务器安装软件 我用ssh连接服务器 登录服务器:ssh root@212.129.*.* , 然后输入密码(从腾讯云管理后台进行密码的设置和获取)进入服务器,然后安装软件,如下~ 1、git:用于代码管理 2、nvm:用于管理node版本 3、node:用于启动web服务 4、pm2:用于守护node进程 安装git [代码]yum install git -y [代码] 下载nvm [代码]git clone git://github.com/creationix/nvm.git ~/nvm [代码] 设置nvm 自动运行; [代码]echo "source ~/nvm/nvm.sh" >> ~/.bashrc source ~/.bashrc [代码] 查询node版本 [代码]nvm list-remote [代码] 安装node.js [代码]nvm install v10.16.3 [代码] 使用nodejs [代码]nvm use v10.16.3 [代码] 使用npm安装pm2 [代码]npm install -g pm2 [代码] 五、下载一个web项目 & 使用 pm2 启动 这里我使用了自己的一个github上的项目:https://github.com/myronliu/ssr-koa-react-redux.git 1、git clone https://github.com/myronliu/ssr-koa-react-redux.git 2、cd ssr-koa-react-redux 3、npm install 4、pm2 start server.js 服务启动之后如下图: [图片] 现在我们可以用IP(服务器的公网ip) + 端口号来进行访问了 [图片] 六、安装nginx 步骤 1: 添加 yum 源 Nginx 不在默认的 yum 源中,可以使用 epel 或者官网的 yum 源,本例使用官网的 yum 源。 sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 安装完 yum 源之后,可以查看一下。 sudo yum repolist 步骤 2: 安装 yum 安装 Nginx,非常简单,一条命令: sudo yum install nginx 常用nginx命令介绍 设置开机启动 $ sudo systemctl enable nginx 启动服务 $ sudo systemctl start nginx 停止服务 $ sudo systemctl restart nginx 重新加载,因为一般重新配置之后,不希望重启服务,这时可以使用重新加载。 $ sudo systemctl reload nginx 步骤3: 打开防火墙端口 sudo firewall-cmd --permanent --zone=public --add-service=http sudo firewall-cmd --permanent --zone=public --add-service=https sudo firewall-cmd --reload [图片] 步骤4: 配置nginx 找到并查看配置文件/etc/nginx/nginx.conf [图片] 步骤5: 配置我们自己的conf [图片] 编写为 [图片] 步骤6: 现在可以使用域名访问我们的网站了,目前是http协议的 [图片] 七、安装证书 将第三步下载的证书里选择Nginx下的两个文件上传到服务器的/etc/nginx/conf.d文件夹下 [图片] Mac上利用scp上传 scp /Users/xxx/Downloads/todo.xxx.com/Nginx/1_todo.xx.com_bundle.crt root@212.129.*.*:/etc/nginx/conf.d [图片] 八、创建https的conf文件 进入到/etc/nginx/conf.d下,执行命令:touch https.conf && vi https.conf [图片] 测试nginx配置 & 重启 [图片] 九、访问https协议的站点 [图片] 备注: 如有问题,欢迎指出! 如有侵权,联系删除~
2019-09-24