之前一直有在维护一个上海地铁线路图的 pwa,最主要的特性就是 "offline first"。但是由于代码都是通过原生的 js 去实现,之前我都不是很喜欢去用框架,不想具有任何框架的偏好。但是到后期随着代码量的增加,代码的确变得混乱不堪,拓展新功能也变得尤为困难。因此,花了将近两个礼拜的时候对于应用进行了一次完整的重构。网站访问地址:https://neal1991.github.io/subway-shanghai

准备

准备工作先做好,在 vue 和 react 之间,我还是选择了后者。基于 create-react-app 来搭建环境,crp 为你准备了一个开箱即用的开发环境,因此你无需自己亲手配置 webpack,因此你也不需要成为一名 webpack 配置工程师了。

另外一方面,我们还需要一些数据,包括站点信息,线路路径,文字说明等等。基于之前的应用,可以通过一小段的代码获取信息。就此如要我们获取我们以前的站点在 svg 图中的相关属性,普通的站点使用 circle 元素,为了获取其属性:

const circles = document.querySelectorAll('circle');
let result = [];
circles.forEach(circle => { let ele = {    cx: circle.cx,  cy: circle.cy,  sroke: circle.stroke,   id: circle.id   };  result.push(ele);
})
const str = JSON.stringify(result);

通过这样的代码我们就可以获取 svg 普通站点信息,同理还可获取中转站信息,线路路径信息以及站点以及线路 label 信息。还有,我们还需要获取每个站点的时刻表信息,卫生间位置信息,无障碍电梯信息以及出入口信息。这里是写了一些爬虫去官网爬取并做了一些数据处理,再次就不一一赘述。

设计

数据准备好之后,就是应用的设计了。首先,对组件进行一次拆分:

组件结构

将整个地图理解成一个 Map 组件,再将其分为 4 个小组件:

  • Label: 地图上的文本信息,包括地铁站名,线路名称

  • Station: 地铁站点,包括普通站点和中转站点

  • Line: 地铁线路

  • InfoCard: 状态最复杂的一个组件,主要包含时刻表信息、卫生间位置信息、出入口信息、无障碍电梯信息

这是一个大致的组件划分,里面可能包含更多的其它元素,比如 InfoCard 就有 InfoCard => TimeSheet => TimesheetTable 这样的嵌套。

组件通信和状态管理

本地开发的最大的难点应该就是这一块的内容了。本来由于组件的层级并不算特别复杂,所以我并不打算上 Redux 这种类型的全局状态管理库。主要组件之间的通信就是父子通信和兄弟组件通信。父子组件通信比较简单,父组件的 state 即为子组件的 props,可以通过这个实现父子组件通信。兄弟组件略为复杂,兄弟组件通过共享父组件的状态来进行通信。假如这样的情景,我点击站点,希望能够弹出信息提示窗,这就是 Station 组件和 InfoCard 组件之间的通信,通过 Map 组件来进行共享。点击 Station 组件触发事件,通过回调更新 Map 组件状态的更新,同时也实现了 InfoCard 组件的更新。同时为了实现,点击其它区域就可以关闭信息提示窗,我们对 Map 组件进行监听,监听事件的冒泡来实现高效的关闭,当然为了避免一些不必要的冒泡,还需要在一些事件处理中阻止事件冒泡。

InfoCard 是最为复杂的一个组件,因为里面包含了好几个 icon,以及状态信息的切换,同时需要实现切换不同的站点的时候能够更新信息提示窗。需要注意信息提示窗信息初次点击信息的初始化,以及切换不同 icon 时分别显示不同的信息,比如卫生间信息或者出入口信息,以及对于时刻表,切换不同的线路的时候更新对应的时刻表。这些状态的转化,需要值得注意。另外值得一题的点就是,在切换不同站点的时候的状态,假如我正在看某个站点的卫生间信息的时候,我点击另外一个站点,这时候弹出的信息提示窗应该是时刻表信息还是卫生间信息呢?我的选择还是卫生间信息,我对于这一状态进行了保持,这样的用户体验从逻辑上来讲似乎更佳。具体实现的代码细节就不一一说明了,里面肯能包含更多的细节,欢迎使用体验。

性能优化

以上这些的开发得益于之前的维护,所以重构过程还是比较快的,稍微熟悉了下 react 的用法就完成了重构。但是,在上线之后使用 lighthouse 做分析,performan 的得分是 0 分。首屏渲染以及可交互得分都是 0 分,首先来分析一下。因为整个应用都是通过 js 来渲染,而最为核心的就是那个 svg。整个看下来,有几点值得注意:

  • 代码直接将 json 导入,导致 js 体积过大

  • 所有组件都在渲染的时候进行加载

找到问题点,就可以想到一些解决方案了。第一个比较简单,压缩 json 数据,去除一些不需要的信息。第二个,好的解决办法就是通过异步加载来实现组件加载,效果明显,尤其是对于 InfoCard 组件:

同步

class InfoCard extends React.Component {  constructor(props) {    super(props) {   ... }    }    ...
}

异步

export default function asyncInfoCard (importComp) {    class InfoCard extends React.Component {    constructor(props) {    super(props);   this.state = { component: null };  }   asyncComponentDidMount() {  const { default: component } = await importComp(); this.setState({ component: component    })  }    }
}

这样我们就实现了将同步组件改造成一个异步加载的组件,这样就无需一下子加载所有的组件。这样我们就可以在 Map 中使用异步的方式来进行组件的加载:

import asyncInfoCard from './InfoCard'
const InfoCard = asyncInfoCard(() => import('./InfoCard')

通过上线之后的性能分析,lighthouse 性能评分一下子就上升到了 80 多分,证明这样的改进还是比较有效的。另外一个值得提的点就是首屏,因为历史原因,整张图 svg 中元素的位置都是定死的,及横坐标和纵坐标都已经是定义好的,而 svg 被定为在中间。在移动端加载时,呈现的就是左边的空白区域,所以给用户一种程序未加载完毕的错觉。之前的版本的做法就是通过 scroll 来实现滚动条的滚动,将视图的焦点移动到中间位置。这次的想法是通过 transform 来实现:

.svg {
transform: translate(-100px, -300px)
}

这样实现了整个 svg 图位置的偏移,使用 lighthouse 进行分析,性能分降到了 70 多分。继续想想有没有其他的方法,后来我想在最左上上角定义一个箭头动画。

<img src="right_arrow.png" alt="right arrow" title="right arrow" class="right-arrow"/>
.right-arrow {  animation: moveright 3s linear infinite;
}
@keyframs moveright {  0% {    transform: translateX(2rem);    }   50% {   transform: translateX(3rem);    }   100% {  transform: translateX(5rem);    }
} 

这样我们就可以创建一个循环向右移动的动画,提示用户向右滑动。部署之后发现性能分立马降到 0,索性也就放弃了这个做法。最后来时决定采用 transform:translateX(-200px)translateY(-300px); ,因为这样通过 css3 的属性可以在一些移动设备上还可以利用 GPU 加速,并且 translateX 不会引起页面的重绘或者重排,只会导致图层重组,最小避免对性能的影响。

部署

目前的部署方案是采取 create-react-app 的官方建议,通过 gh-pages 实现将 build 的打包文件上传到 gh-pages 分支上从而实现部署。

兼容性

目前该应用在 Chrome 浏览器的支持性是最好的,安卓浏览器建议安装 Chrome 浏览器使用,我一般也都比较喜欢在手机上使用谷歌浏览器。对于 Safari 浏览器,其它的浏览功能似乎没有什么大问题,目前应该还没支持添加到主屏幕。不过在之后的 ios 版本好像对于 pwa 有着更进一步的支持。

结语

花了两个礼拜的时间完成了项目的完整的重构,从这一年来的 commit 记录可以看到三月份疯狂 commit 了一波,主要是第一个周末花费了两天的时间修改了好多代码,被那个 InfoCard 的状态切换搞了很久,后面就是针对性能做了一些优化。过程很痛苦,一度怀疑自己的 coding 能力。不过最后还是有以下感悟:

  • 世界上没有最好的语言,最好的框架,只有最合适的

  • 最优雅的实现都不是一蹴而就的,都是一个个试出来的

最后一个冷笑话:

青年问禅师:“请问大师,我写的程序为什么没有得到预期的输出?” 禅师答到:“年轻人,那是因为你的程序只会按你怎么写的执行,不会按你怎么想的执行啊……”

阅读原文,可以查看项目源代码地址,欢迎 star 或者 pr。

以上

本来想上传介绍视频的,腾讯视频的实名认证。简直可怕。。。

pwa, 上海地铁线路图全新重构.相关推荐

  1. html5制作线路图,HTML5绘制上海地铁线路图

    某市政项目用到地铁图展示,展示地铁站点以及相关信息流,使用Qunee组件可以很好的解决这类需求,做出优美的展现,下面以上海2012地铁图为例,效果如下: 上海2012地铁图效果 示例讲解 首先需要解决 ...

  2. php线路图,HTML5绘制上海地铁线路图-大前端

    某市政项目用到地铁图展示,展示地铁站点以及相关信息流,使用Qunee组件可以很好的解决这类需求,做出优美的展现,下面以上海2012地铁图为例,效果如下: 示例讲解 首先需要解决数据问题,可以从维基百科 ...

  3. HTML5绘制上海地铁线路图

    某市政项目用到地铁图展示,展示地铁站点以及相关信息流,使用Qunee组件可以很好的解决这类需求,做出优美的展现,下面以上海2012地铁图为例,效果如下: 示例讲解 首先需要解决数据问题,可以从维基百科 ...

  4. 绘制地铁线路html,HTML5绘制上海地铁线路图(2)

    创建图元 数据需要转换成qunee图元对象,三种类型分别对应三个创建函数 创建文本标签 function createText(name, x, y, rotate){ var text = grap ...

  5. 地铁线路图显示效果php,基于HTML5制作在线上海地铁图

    某市政项目要用到地铁图,展示上海地铁站点以及相关信息流,尝试使用HTML5技术来实现,自己折腾有点慢,找到一个HTML5的图形组件-Qunee for HTML5,简单学习一下,就可以很好的解决这类需 ...

  6. 上海轨道交通十一号线,上海地铁十一号线,上海地铁11号线,上海地铁十一号线线路图...

    规划中的上海轨道交通11号线类似北京的地铁十号线,采取主线和支线两条线路,主线由嘉定新城到浦东三林(一期 ),支线在西北段上连接上海国际赛车场和安亭汽车城.线路总长 60公里.二期由浦东三林到南汇临港 ...

  7. 地铁线路图的设计与实现

    在北京.上海这样的一线城市,地铁绝对是上班族的首选交通工具,尽管有时挤得要命,但你真的找不出比地铁更准点的交通工具了.平时出门,我也总是习惯于在百度地图或丁丁地图里先查询一下地铁乘车路线,这些程序用起 ...

  8. DayDayUp:上海地铁线路高清图、1~17号地铁线路各站点名称及对应路线集合之详细攻略

    DayDayUp:上海地铁线路高清图.1~17号地铁线路各站点名称及对应路线集合之详细攻略 目录 上海高清地铁线路图集合 1.实时查询地铁线人流量拥挤度 1.1.网页端查询 1.2.手机APP端查询 ...

  9. 如何便捷绘制上海地铁图?

    地铁图的首要目标是方便大家查找路线和目的地,因此直观便捷更重要.过度的.花里胡哨的地图设计反而会适得其反. 有个个人网站上传的地铁图(传送门在下方)就非常直观,而且背景加上了地理图,大家一眼就能看出某 ...

最新文章

  1. Python3 使用replace 替换空格无效
  2. 丹佛斯变频器模拟量速度输入_变频器与PLC是如何协同作战的?了解这几个控制方式很有必要...
  3. 查看Windows系统里的进程已运行的时间
  4. 删除fedora多余内核:解决每次升级后旧内核还会存在的问题
  5. 什么事数据科学_如果您想进入数据科学,则必须知道的7件事
  6. Hadoop之mapReduce有几种排序及排序发生的阶段
  7. UVA 1615 Highway 高速公路 (区间选点)
  8. C语言函数一章教学,c语言案例教程:函数教学讲义.ppt
  9. php gd support = off,PHP GD库中GD函数的用法详解
  10. DCC2020:VVC帧内预测中基于梯度的CU划分早停止
  11. 苹果笔记本macbook pro如何安装python_Mac OS系统下的安装
  12. matlab 广义特征,特征值 特征向量 广义特征值 matlab
  13. 一个孩子的心愿(大冰,感动)
  14. minecraft崩溃java,je1.7.10,进入世界就崩溃,解决一下
  15. android profile 打包_Android 利用 Managed Profile 确保兼容性
  16. Pr速成3小时学会视频剪辑[副业学习会]
  17. sklearn Gaussian Processes(高斯过程)
  18. d3-绘制直线-横线
  19. GIS开发之二维地下管线综合管理系统(Arcgis)第四节 使用arcgis api for js 加载天地图
  20. SICP学习笔记及题解---构造过程抽象(一)

热门文章

  1. 如何简单地给视频添加背景音乐?
  2. 长臂挖掘机建模要如何运用MapleSim
  3. 解析法实现一元线性回归
  4. [译] 深入 Flutter 之手势
  5. html 超链接标签
  6. 国际名牌服装有哪些?
  7. 全球抗击雾霾的那些事儿
  8. YDOOK:Blander 2D 怎样新建图层
  9. UVa 10074 Take the Land (转化思想最大子矩阵和)
  10. 机器学习基础算法(2)