前端热力图系统实现

1. 背景

今年6月开发了一个前端热力图系统,目前已经应用在公司的主要业务中,一直没时间做个总结,现在梳理一下实现思路。

先说下为什么要做这样一个系统,电商网站的一种常用营销手段,就是配置眼花缭乱的活动页展现给用户,有的用来展示各种商品,有的供用户领取优惠券。但屏幕的展示空间有限,如何配置不同的模块才能最大化利用页面空间,一种比较好的方式就是采集用户点击数据,绘制出热力图,供产品、运营和设计同学参考,不断优化模块配置,有效提升点击转化。

2. 系统架构

热力图架构

先将热力图系统进行子功能拆分,可以得到以下几个部分:

  1. 用户点击数据采集:包括页面埋点、数据入库;
  2. 热力图绘制:包括:数据读取、数据加工、热力图绘制;
  3. 数据查询平台:主要是按日期和活动ID查询自定义区域的点击数。

2.1 数据采集

数据采集部分,主要通过事件代理在body上绑定click事件,采集数据主要包括:

  1. x:点击事件触发相对于 document 的横坐标,主要取自于event.pageX
  2. y:点击事件触发相对于 document 的纵坐标,主要取自于event.pageY


阅读全文 »

按钮加载效果实现

1. 背景

之前的加载大部分都是 toast 的形式,但现在一些 Native 的按钮点击都将加载动画做在了按钮上,给人感觉很好,比较常见的就是移动端支付宝的支付按钮,点击后有个旋转的圆圈表示加载。现在也来模拟一个该效果。

2. 实现

看到这种线段变化的动画,直接想到之前用的 svg 配合 strokeDasharray 属性来实现,具体效果如下:

See the Pen DynamicButton by zee (@bt404) on CodePen.

注:转载注明出处并联系作者,本文链接:https://nodefe.com/button-animation/


阅读全文 »

WebView 中判断键盘是否弹出

1. 背景

最近在开发中遇到这样一个问题:页面中有一个输入框input,一个 Modal 框modal,一个点击打开 Modal 框的按钮btnOpenModal,和一个提交表单的提交按钮submit。当input获得焦点,键盘弹出时,如果直接点击btnOpenModalinput会失去焦点键盘收回,导致页面高度改变,modal弹出后会有一个闪烁。所以需要在点击btnOpenModal时,判断当前键盘是否弹出。

2. 方案

最初,考虑在点击按钮时获取输入框元素,判断它是否处于focus状态,来判断键盘是否已弹出。但点击按钮之后,按钮就成为了document.activeElement。此时输入框必然已失去焦点,导致无法通过该方法判断键盘是否弹出。

考虑监听输入框的focusblur两个事件,当focus触发时,为当前视图容器this.cnt添加一个标识class,假设为KEYBOARD_STATUS;当blur触发时,则移除该class。当点击btnOpenModal时,判断this.cnt是否拥有KEYBOARD_STATUS这个class。如果有,则说明


阅读全文 »

单页视图引擎(SPA)添加视图切换动画

1. 背景

9月的时候,为单页试图引擎 Cyra 2.1.0 版本增加了一个创建视图切换动画的功能,使得 i 版页面之间切换也可以有一个滑入滑出的效果。一直没有时间做一个梳理,现在记录一下。最终的效果如图:

Cyra Animation

补充:悲剧,刚修复了一个 bug,如果想要设置ANIMATION_FUNC,则需要升级到v2.1.4

2. 思路

其实思路比较简单,就是在 Router 中控制。首先,将所有视图容器position都设置为absolute。当切入视图完全渲染出来后(也就是执行完willAppear钩子函数),设置当前展示视图和即将展示的视图两者的 animation CSS 属性,在 animation 中修改视图容器的translateX来实现动画效果。

因为每个视图容器是单例的(Mix 页除外),当项目中出现循环路径,如:

View A -> View B -> View C -> View A

或者当 A 和 C 是同一个视图,只是数据不同。这种情况下,如果视图右滑,也就是用户主动通过点击“返回”按钮进入(返回)前一个视图时,当前视图向右滑出。如果滑出后不修改它


阅读全文 »

Chrome devtools 扩展与模块间通信

1. 背景

为了方便 Cyra 用户查看/管理项目中的视图及视图间的跳转和数据传递,决定开发一个 Chrome extension(扩展)来方便展示。开发中,最关键的问题在于如何解决各模块之间的通信。最终的实现效果如下图:

Cyra devtools

2. 开发

2.1 模块划分

这部分网上已经有很多文章来介绍,在此不再赘述。但网上涉及到devtools类型的插件比较少,所以简单介绍下。首先,在 devtools 中创建的 panel 本质是一个 HTML 页面。代码主要分为以下4部分:

  1. background.js:Chrome 为扩展提供的一个独立的脚本运行环境,在本例中,主要作为 content_script.js 和 devtools.js 之间通信的桥梁,因为 Chrome 并没有提供后面二者直接通信的服务。
  2. content_script.js:用来向打开的页面注入脚本。
  3. devtools.js:用来在 devtools 中创建一个 panel,并实现该 panel 和 background.js 之间的通信。
  4. draw.js:devtools.js 中创建 pan


阅读全文 »

记一次 Nginx URI rewrite 优化

1. 背景

既上一篇文章记录了组内单页引擎升级路由为 History API 方式,考虑到不支持该方式的浏览器/WebView,需要多页降级。如果不对 Nginx 进行配置,就会出现404,因为多数通过pushState得到的 URL 并没有真实对应的资源。

2. URI 形式

采用 History API 方案的 URL 格式如下:

http[s]://hostname/resource/project/page[/view]?arg1=value1

其中 resource 为资源目录,该目录下放置各个不同的项目文件夹,每个项目对应一个 project 目录。一个 project 中可能会有多个单页应用,每个单页应用对应 URL 中的一个 page。一个单页应用下的不同视图分别对应一个 view。因为采用的单页引擎支持默认路由,所以 view 并非必需。

比如 A 项目下有一个 refund 的单页应用,对应的 HTML 资源为 refund.html,为了 URL 的美观,在 URL 中去掉 html 后缀名。refund 管理两个视图 detail 和


阅读全文 »

History API 路由方案 Nginx 配置

1. 背景

之前使用 History API 作为路由方案升级了组内单页视图引擎。完整的实现还需要 Nginx 的辅助配置,因为当页面刷新时,通过pushState到达的路由很可能并不存在对应的资源,所以要使同一个项目下的所有路由匹配到项目对应的单页资源。

2. 方案

其实主要的工作是对3种资源的 uri 配置 rewrite:

  1. HTML 资源
  2. 引用到的项目 JS 资源
  3. 引用到的项目 img 资源

对应的配置如下:

其中/resource/wa/为所有项目所处的统一目录,第二级目录为项目所在目录,第三级对应项目下的某个单页,第四级为可选,且表示某个单页项目下的某个具体 view。

注:转载注明出处并联系作者,本文链接:https://nodefe.com/history-api-nginx-config/


阅读全文 »

使用 History API 升级 SPA 路由方案

1. 背景

组内现在使用组内自己开发的移动端 SPA 引擎 Cyra,版本为 1.2.x。框架很轻,使用 hash 做路由,然后统一管理各个 view(视图)的状态以及 view 之间跳转。

开发过程中,遇到很多坑都和使用 hash 做路由有关。当然,问题并不在 hash 本身,而是和客户端以及后端 RD 配合中遇到的问题。比如:

  1. 验签问题,客户端没有将 hash 计算进去;
  2. 客户端会在 WebView 中访问的 URL 上拼接一些参数,作为客户端和前端之间通信的一种手段。由于拼接算法有问题,导致部分参数拼接到了 hash 后面,使得 Cyra 多个 view 之间通过 URL 传递的参数格式被破坏;
  3. iOS 版 APP 的 WebView 中通过 jsbridge 来修改 title,但是 hash 的修改无法修改。

综上,考虑升级框架的路由实现方案,使用 History API 代替 hash,并配合 Session Storage 做多页降级,以此来解决上述问题。

2. 方案

为兼容 Cyra 1.x 开发的项目,此次升级仅修改路由的内部实现方式,


阅读全文 »

iOS 8.1/8.2 第三方输入法无法响应 keyup 事件

这是前两天遇到的一个坑。场景是一个包含一个输入框和一个“提交”按钮(默认 disabled)的 H5 页面,当在输入框中输入合法数据后,“提交”按钮会变得可用,然后用户可以点击提交数据。

之前的实现是监听输入框的keyup事件,当输入合法数据后,修改按钮样式变为可用。

但部分用户反馈无论输入什么样内容,按钮始终不可点击,所以猜测可能是keyup事件没有响应。通过整理出问题的 OS,发现主要是 iOS 8.1/8.2两个版本。测试后发现的确是这两个版本的 WebView 下第三方输入法对keyup事件无响应。

最终的解决方案是修改为监听input事件,问题解决。先写这么多,留坑总结下两个事件的不同。

注:转载注明出处并联系作者,本文链接:https://nodefe.com/keyup-dont-work-under-ios8_1-and-ios8_2/


阅读全文 »

stringify 实现及 JSON 数据类型思考

背景

记之前遇见的一道面试题,让现场写出 JavaScript 中stringify函数的实现。首先写一下自己最开始的思路,然后针对里面的一些问题进行逐步修改,并且引出对 JSON 这种轻量级数据传输格式所拥有数据类型的学习与思考。

一、最初实现

首先给定题目

将一个 JSON 格式对象转换为字符串,转换后的结果可以通过JSON.parse()方法将该字符串重新转换为一个 JSON 对象。

先简化问题,将数据类型简单划分(并不正确,下文会给描述)为:对象(Object)、数组(Array)和字符串(String)这三种类型。对于 String,只需要简单的对它进行toString()调用,并包裹在"中处理,对于前两者则需要采取不同操作。考虑到他们的子元素也是 JSON 对象,定义具有递归性,所以代码也采用递归来实现。最初代码如下:

代码的思路就是传入一个对象,然后判断它的类型:1. 如果非数组和对象类型,就直接返回它的字符串形式;2. 如果是对象类型,则遍历每个键值对,判断每个键对应值的类型,如果是


阅读全文 »