继之前的一篇《记前端项目首屏加载优化(打包篇)》之后,这次来讲一讲我的首屏加载在网络方面的优化😏。
资源加载是一个网站的展示在用户浏览器的必经之路,资源的请求次数和响应时间决定了网站的加载体验。本篇主要针对请求次数和响应时间聊一聊优化过程。
有很多工具可以检测网站的网络请求:比如WebPage Speed Test,Instant Website Test,这些工具可以分析网站的加载速度、安全性、可靠性等很多方面。而我是希望能够在本地开发过程中分析并优化,所以我还是选择Chrome Devtool 里的Network panel 进行分析。
本地开发环境的资源跟线上是有所不同的,本地的资源一般没有压缩,而线上有压缩,这样就会造成测试环境不真实,所以需要在本地模拟线上的打包环境,编译出跟线上环境一样的包来加载,这样分析出来的结果才有意义。
在上一篇《记前端项目首屏加载优化(打包篇)》有写到过在本地打包一个和线上环境一致的压缩配置,在package.json加入下面的配置:1
2
3
4"scripts": {
...
"local_production": "cross-env NODE_ENV=local_production npm run build"
}
然后在webpack配置里面判断process.env.NODE_ENV === ‘local_production’,便构建出production环境的包即可。
打包出来的包大小1.7M,所有的依赖包index.xxx.js有748k这么大,所以需要开启gzip压缩,可以大大减小加载大小,首先安装express 的gzip包 compression
:1
npm install -D compression
然后添加express中间件:1
2var compression = require('compression');
app.use(compression());
打包后启动服务器,浏览器访问,这时候index.xxx.js已经压缩到212kb😎
http1.x时代的优化折磨好长一段时间,各种奇淫技巧为了弥补http1的短板,影响着我们的开发专注度,好在http2已经开始盛行,相信不久的将来可以完全替代http1。
现在基本主流的浏览器都支持http2.0了
http2.0大幅提升了加载性能,相比http1增加了多路复用、二进制分帧、header压缩等特性
开启http2也很简单,前提是开启了https协议,只要在Nginx配置文件中找到你要开启http2.0的域名server模块,然后将 listen 443 ssl;改成 listen 443 ssl http2; 即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14server {
listen 443 ssl http2;
server_name domain.com;
ssl_certificate /path/to/public.crt;
ssl_certificate_key /path/to/private.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #允许的协议
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; #加密算法(CloudFlare 推荐的加密套件组)
ssl_prefer_server_ciphers on; #优化 SSL 加密套件
ssl_session_timeout 10m; #客户端会话缓存时间
ssl_session_cache builtin:1000 shared:SSL:10m; #SSL 会话缓存类型和大小
ssl_buffer_size 1400; # 1400 bytes to fit in one MTU
检查请求是否已经开启http2,可以在chrome的network面板点鼠标右键,选中protocol,就可以看到该请求是否开启了http2,图中h2就代表http2:
顺便提一句,很多第三方的SDK都没有开启http2🤔
项目开发中用到的静态资源和打包后的资源都可以传到cdn以提高加载速度,我的项目是用七牛的上传脚本,在npm run build
之后执行上传脚本将dist
目录上传到cdn上,注意在webpack配置里要配置publish_path
对应上传cdn后的域名路径。具体看七牛的上传脚本文档
构建线上环境的publicPath的值可以是cdn的域名+路径的形式,结尾要加/
:"https://cdn.xxx.com/static/
webpack的output
里配置publicPath
:1
2
3
4output: {
path: DIST_PATH,
publicPath: publicPath,
},
webpack的图片资源Loader应添加相应配置publicPath
:1
2
3
4
5
6
7
8
9
10{
test: /\.(jpg|gif|ico|png|svg|mp4|mp3)$/,
use: `url-loader?limit=10&name=asset/[name]_[hash:5].[ext]&publicPath=${config.PUBLIC_PATH_ASSET}`,
exclude: /(node_modules)/,
},
{
test: /\.(woff|eot|ttf)\??.*$/i,
loader: `url-loader?limit=1000&name=fonts/[name].[hash].[ext]&publicPath=${config.PUBLIC_PATH_ASSET}`,
exclude: /(node_modules)/,
},
有些时候我们引入的第三方插件是不能被webpack优化按需加载,或者是没有提供模块化加载的(只能通过script标签引入的)插件,比如像tinymce富文本编辑器,由于它的加载方式是靠script标签引入tinymce.js,并靠tinymce.js里自身的加载机制去另外加载主题theme.js或者相关plugin.js。
这种第三方插件无法做成模块化。这样的话我们是不是就只能在入口index.html加script标签引入它,被迫在首屏加载这个暂时用不到的大文件🤔?
我找到了webpack-require-http来帮忙😎,他可以帮我用require
语句帮我加载远程js,并提供了回调🤝,于是我便可以控制在什么时候加载tinymce🤘。
我的项目是按路由按需加载的,首页没有用到富文本编辑器所以没必要加载,而论坛页需要用到富文本编辑器,于是我们可以在react-router
路由配置里的添加onEnter钩子,在进入论坛页的时候才加载tinymce。
1 | // 在需要tinymce编辑器的路由时提前加载 |
这样子如果访问首页的话,首屏就少加载了这个big guy,经测试,在模拟1M/s 的网速下访问网站,出现首屏界面的时间快了2s左右,2s! 😱…,这可以说是最大力度的一次优化了。
缓存有很多种方式,大部分是服务器端处理的,而客户端处理的则一般是把资源缓存在本地。
比较有效的本地缓存一般是用application-cache
或者service worker
将网站的资源缓存到本地,再次访问时直接调用本地的缓存资源,几乎是本地打开的速度,但是我的项目里的资源用到了CDN配置,所有的资源都在CDN外链中,而刚才提到的两种缓存方式都不支持缓存跨域资源😞。
这时候就得权衡一下了🤔,如果是首次访问网站的浏览器,加载时间cdn服务器会比自身服务器快一些,而二次加载则后者更快,只是网站缓存需要自己维护一套配置,如果配置不得当,会落下很严重的坑😂,所以目前我还是先选择相对保守的CDN加载,等以后慢慢磨合🙂。
]]>网上有好几种单页应用转seo的方案,有服务端渲染ssr、有预渲染prerender、google抓AJAX、静态化。。。这些方案都各有优劣,开发者可以根据不同的业务场景和环境决定用哪一种方案。本文将介绍另一种思路比较清奇的SEO方案,这个方案也是有优有劣,就看读者觉得适不适合了。
我的项目是用react+ts+dva技术栈搭建的单页应用,目前在线上已经有几十个页面,若干个sdk和插件在里面。
以前写过一种单页应用seo的方案,就是自己先在本地用爬虫做预渲染,生成同样目录结构的静态化的html,前端项目服务器判断请求的UA是搜索引擎蜘蛛的话就会转发到我事先静态化过的html页面
当时的项目只是一个简单的只有几个页面的企业官网,预渲染没啥问题。
跟着这个思路,只要判断搜索引擎蜘蛛让蜘蛛看到另一个有数据的页面不就行了。
至于页面长什么样,蜘蛛🕷才不会管呢,就像是你找广告商投放广告,广告商不会要求你要怎样的主题什么色调,只要你按照他的尺寸和要求来做,然后给钱给货就完事了🤑。
所以可以针对SEO做另一套网站,没有样式,只有符合seo规范的html标签和对应的数据,不需要在原有项目上改造,开发成本也不会很高,体积小加载速度更快。
缺点也有,就是需要另外维护一套网站,主网站界面变化不会影响,如果展示数据有变化就需要同步修改seo版的网站。
先建个单独的seo文件夹,不需要动到原有项目,下面是代码结构:
代码实现非常之简单,只要写一个中间件拦截请求,鉴别蜘蛛,返回对应路径的seo页面即可。
我的前端服务器是用express,可以写个express的中间件, 新建server.js:
1 | // seo/server.js |
然后在前端的启动服务器里加入这个中间件,记得要放在其他中间件之前
1 | // 前端启动服务器的server文件 |
接下来就是写模板和对应的解析了, 新建一个home文件夹,文件夹下再建一个index.ejs和index.js
1 | <!-- seo/src/home/index.ejs --> |
index.js用于解析对应的ejs模板1
2
3
4
5
6
7
8
9
10
11// seo/src/home/index.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './index.ejs'), 'utf8');
// 这里为什么会有个async关键字,往后面看就可以知道。
module.exports = async (req) => {
const result = ejs.render(template)
return result
}
我们还可以建多个layout模板来管理head、title和导航栏这些公有的元素
1 | <!-- seo/layout.ejs --> |
解析layout.ejs,套入内容的layout_render:1
2
3
4
5
6
7
8
9
10// seo/layout.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './layout.ejs'), 'utf8');
const layout_render = (children) => {
return ejs.render(template, {children: children})
}
module.exports = layout_render
路由表用简单的键值对就可以了,键名用字符串形式的正则来表示路径的匹配规则:1
2
3
4
5
6// seo/routes.js
const home_route = require('./src/home/index')
module.exports = {
'^(/?)$': home_route,
}
那么数据如何做请求并展示到对应的模板内呢?数据请求是异步的,怎样等到请求完成再渲染模板呢?
我们可以用async/await来实现,现在来做一个社区的帖子列表页面,需要先请求社区下帖子列表数据再把数据渲染到模板,新建一个community文件夹,同样再建一个index.ejs作为帖子列表页面模板:1
2
3
4
5
6
7
8
9<!-- seo/src/community/index.ejs -->
<div>
<h1>帖子列表</h1>
<ul>
<% forum_list.map((item) => { %>
<li><a href="/community/<%= item.id%>" target="_blank"><%= item.title-%></a></li>
<% })%>
</ul>
</div>
相关的接口请求及数据操作写在同级的index.js:1
2
3
4
5
6
7
8
9
10
11
12// seo/src/community/index.js
const ejs = require('ejs')
const fs = require('fs')
const path = require('path')
const template = fs.readFileSync(path.resolve(__dirname, './index.ejs'), 'utf8');
const axios = require('axios');
module.exports = async (req) => {
const res = await axios.get('http://xxx.xx/api/community/list')
const result = ejs.render(template, {forum_list: res.data.list})
return result
}
再加上对应的路由配置:1
2
3
4
5
6
7
8// seo/routes.js
const home_route = require('./src/home/index')
const community_route = require('./src/community/index')
module.exports = {
'^(/?)$': home_route,
'^/community$': community_route,
}
这样就实现了先取接口数据再做渲染,保证了蜘蛛访问能给到完整的数据和html结构。
继续实现一个帖子详情的页面:1
2
3
4
5
6
7<!-- seo/src/community_detail/index.ejs -->
const community_route = require('./src/community/index')
<div>
<h1><%= forum_data.title%></h1>
<p><%= forum_data.content%></p>
<p>作者:<%= forum_data.user.nickname%></p>
</div>
1 | // seo/src/community_detail/index.js |
同样加上对应的路由配置:1
2
3
4
5
6
7
8
9
10// seo/routes.js
const home_route = require('./src/home/index')
const community_route = require('./src/community/index')
const community_detail_route = require('./src/community_detail/index')
module.exports = {
'^(/?)$': home_route,
'^/community$': community_route,
'^/community/\\d+$': community_detail_route,
}
这样就实现了一个简单的seo版网站,不需要任何样式,不需要js做弹框之类的后续交互,只要蜘蛛访问网址的第一个请求有它要的数据即可,是不是非常的清奇😝。。。
总结来说呢,就是如果你的项目处在线上运营阶段并且开发到了一定的集成度了,迫于ssr的改造成本太大,又需要让一些数据(比如每一篇文章帖子)能够被收录,就可以考虑一下我的这个方法🤓。
但是我不保证蜘蛛的防作弊机制,会不会过滤掉我这种跟浏览器正常访问主站差异较大的seo版小网站🤔。目前这个方案还在试验阶段。
测试也很简单,写个模拟蜘蛛请求即可,curl、爬虫、postman都可以模拟蜘蛛的UA来测试。或者改一下搜索引擎蜘蛛的的判断条件就可以直接用浏览器访问的呢。
如果有朋友用了我这个方法并且真的有用能够被搜索引擎收录的话,请记得我😎,要是能打赏就更好了哈哈🤑。
]]>看了一下我司官网的webpack打包出来的大小情况,发现有很多可以优化的点,比如 lodash、moment.js、antd等等;
本文主要围绕webpack的打包优化,并根据业务情况适当的做减法。
优化前一定要有一个界面能记录目前的打包情况,推荐用webpack-bundle-analyzer这个包, 它可以看到打包后每个模块的大小,还能给出gizp压缩后的大小,在生产环境中加载的模块都是经过gzip压缩过的,可以作为真实访问的大小依据。
安装也很简单:1
2
3
4
5
6
7
8
9
10
11// cli
npm install --save-dev webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
注意生产环境(production)是代表线上真实的环境,所以analyzer要对生产环境的包进行分析的,所以我配置了一下本地打包生产环境的构建配置,在package.json加入下面的配置:1
2
3
4"scripts": {
...
"local_production": "cross-env NODE_ENV=local_production npm run build"
}
然后在webpack配置里面判断process.env.NODE_ENV === 'local_production'
,构建production环境的构建并且加入analyzer分析生产环境打包出来的情况。
这里是我的项目用analyzer生成出来的包大小情况(打包前)
主要看index.xxxx.js,它包含了所有的公共依赖,我们要做的就是减少不必要的公共资源的体积,可以减少大量不必要的代码。
从上面的可以看出来antd.less占了很大部分面积,因为我要在项目中自定义theme,但是官方的那套配置的形式来自定义theme只能修改变量,不能改组件,所以我先加载所有的antd.less再在后面接着加载一个theme.less用于修改主题变量和修改antd组件样式。
移除了antd之后index包小了三百多k,这还远远不够,接着看下面的优化点
lodash也是需要优化按需加载的方式的,推荐这篇教程Webpack按需打包Lodash的几种方式, 按照教程改进后,lodash 小了500多k。
其实moment引进来的时候会带有很多语言包的,我们只用到了其中一个中文的包,所以其他语言包都可以去掉,1
2
3plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
]
后来又发现项目中只用到了moment().format()这个方法,由于moment.js只有一个大的moment.js模块,没有按模块分开写,无法按需打包,那么其实我们可以自己实现个简易版的moment来替代moment.js,下面是我找到的实现简易版moment代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76// 简易版moment代替moment.js
class Moment {
private date:Date;
constructor(arg = new Date().getTime()) {
this.date = new Date(arg);
}
padStart(num) {
num = String(num);
if (num.length < 2) {
return '0' + num;
} else {
return num;
}
}
unix() {
return Math.round(this.date.getTime() / 1000);
}
static unix(timestamp) {
return new Moment(timestamp * 1000);
}
format(formatStr) {
const date = this.date;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const week = date.getDay();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
const weeks = ['一', '二', '三', '四', '五', '六', '日'];
return formatStr.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|h{1,2}|m{1,2}|s{1,2}/g, (match) => {
switch (match) {
case 'YY':
return String(year).slice(-2);
case 'YYY':
case 'YYYY':
return String(year);
case 'M':
return String(month);
case 'MM':
return this.padStart(month);
case 'D':
return String(day);
case 'DD':
return this.padStart(day);
case 'd':
return String(week);
case 'dd':
return weeks[week];
case 'ddd':
return '周' + weeks[week];
case 'dddd':
return '星期' + weeks[week];
case 'h':
return String(hour);
case 'hh':
return this.padStart(hour);
case 'm':
return String(minute);
case 'mm':
return this.padStart(minute);
case 's':
return String(second);
case 'ss':
return this.padStart(second);
default:
return match;
}
});
}
}
export const moment = (arg) => {
return new Moment(arg);
};
这样就直接可以把moment.js 干掉了,包体积又小了不少。
下面是优化后的analyzer生成出来的包大小情况
包体从2.7M优化到了1.7M,gzip从297k减小到212k,访问虽然只是快了一点点,但在低网速环境下访问还是看得到区别的。
接下来讲一个跟包大小无关又很重要的优化点,就是单页应用的第一个入口html,正常情况下入口html只是用来加载js包,等js加载完之后才渲染出相关界面出来,这个入口html本身没有内容展示,但它是整个网站的第一个请求,取到这个入口html之后才开始加载js,等到加载完js才开始渲染界面,这段时间是占网站整体加载时间最多的,如下图:
第一个请求只要128ms,直到加载完公共js渲染出界面需要1s左右,这时候如果入口index没内容的话那就是纯粹的白屏时间了,所以我们应该好好利用这个入口index.html,可以做一个骨架屏或者loading动画,能让用户在等白屏时间里能够有个界面能看到,停留时间会更长一些,也能让用户以为这个网站一下就刷出来看到东西的感觉。
对于这个入口index的利用,我是加入了顶部导航栏进去的,让用户可以第一眼看到导航栏知道有什么导航项,而且也是可以点进去的,而内容区对于不同的路径访问会有不同的界面,所以我就简单的弄个loading即可。
至此,这一版优化减少了加载的时间,同时合理利用了入口index作为loading页,提高用户体验。
前端优化工作是一个长期且复杂的工作,有很多可以考虑的地方,可以根据网络环境、框架、用户群体、业务情况、代码结构等多个方面合理地安排选择优化方案,本文只是我对于现有公司官网的优化的一部分,在这里分享给大家,如果觉得有用就点个赞吧👍
]]>相信进来的观众一定很想知道我究竟埋了什么彩蛋🤔,这里说的“彩蛋”,指的是程序里的彩蛋(我的理解是一种区别于程序本身别有洞天的一番景象,并具有一定代表意义的小程序📟),
我埋的彩蛋的是在一个网站里的,彩蛋必须是玩家自己找到才有用的😎,先看看下面的彩蛋提示再进入网站找彩蛋哈😜。
埋彩蛋的程序是编程猫官网,用浏览器访问,再打开浏览器的开发者工具的控制台📟。
可以看到一个大大的codemao字符画(如果看不清楚可以拉大控制台的窗口大小),就是下面这个字符画
1 | █████ |
发挥你们的探索能力🤓,看看能不能在控制台里找到跟这个字符画有关的彩蛋。
找到的话如果觉得有意思的话可以点个赞👍或者发评论提点提点🙏,有更好的想法都可以提哈🤘。
没找到的话下面就是彩蛋答案📋,但是记住彩蛋必须是玩家自己找到才有用的🤗,所以想清楚再决定看不看答案。
其实很容易能找到彩蛋,当你看到控制台上那么显眼的codemao字符画时,其实重点不在字符画,而是codemao
这几个字符,尝试在控制台里输入codemao
,你会发现codemao
是一个全局变量,按回车打印出来的值就是彩蛋入口了!
而打印出来的值就是”恭喜少年,你找到了编程猫的第一颗彩蛋,输入 start(); 后即可开始《编程猫躲车车》游戏!”,继续在控制台输入start()
即可触发彩蛋游戏。
为啥突然想到要在自己开发的网站上埋这个彩蛋呢🤔,可能是因为开发工作时间长了👨💻,想在工作中增加点趣味👩🎨,也可以给用户一个耳目一新的感觉,自己擅自加了这么个需求哈哈😜。做完这个彩蛋之后给到产品经理看,觉得很有意思,而且具有传播价值,所以值得一试。
然而有个运营同事听闻此事后却跟我说起教来,“彩蛋应该怎么做才有价值,才能运营起来blablabla”的😳,我是听不懂,作为一个程序员,我觉得不要为了“埋彩蛋”而“埋彩蛋”,我做这个彩蛋的时候没有太多想法🙄,并不是考虑怎么运营才怎么设计的,也不是想着怎么设计彩蛋而设计彩蛋。
“史上第一款含有彩蛋的游戏”——《冒险/魔幻历险(Adventure)》深入人心😋,而《Starship 1》的开发者因为年代久远,Ron Milner早已忘记该怎么触发彩蛋,40多年后查了代码费尽周折才找到😝。
我觉得一个程序员在自己的程序里埋彩蛋,完全是出于自己的第一想法😏,这个想法可以是像Warren Robinett一样为了打破雅达利的约束让自己的名字留在游戏里😠,也可以像Ron Milner一样为了让玩家探索彩蛋,发现自己的名字,得到10条命的奖励🤑。
而我设计这个彩蛋的想法😉,是因为我发现了可以在控制台里跑起来的字符小游戏《编程猫躲车车》,这有别于编程猫创作工具做出来的作品,一贯的在网页里运行的小游戏,同样是简单的操作,放到控制台运行却别有一番天地,虽然画质低劣又清奇,体验也差,但足以达到我理解的彩蛋效果,有别于程序本身的小游戏,codemao代表了编程猫,说不定还能激发用户的创作欲望和创意呢。。。
以上纯属个人观点,如有写的不正当的地方可以权威批评
其实这个彩蛋游戏是从网上搬过来的,只是换了猫和车而已,感谢知乎大佬小芋头君的分享,啥也不说了,源码奉上:
1 | (function(){ |
工作中遇到一个这么一个需求:这是一个多图上传的场景,如果用户上传选择多张图片,则上传后直接展示多张图片,如果上传的图片是gif动图,则需要分解这张动图拆分成一帧一帧的单张图片,再按顺序展示出来。
这里最核心的就是2,3步,非常庆幸有https://github.com/buzzfeed/libgif-js 这个库,才得以实现后面的步骤;
由于是公司项目就不展示界面和完整代码,只放关键代码:
1 | import { SuperGif } from './libgif.js' |
1 | // 判断文件格式并展示的函数 |
1 | dataURLtoFile(dataurl, filename) { |
至此,核心功能基本实现,上面的函数已经将gif分解成多张图片存放在this.img_list 这个数组里面。
接下来只要拿img_list数组里的file对象上传到服务器即可。
上传方式各不相同,这里就不放具体代码了,需要注意的是,图片上传是异步操作,多图上传需要得知所有的图片全部上传成功才能确定上传完成,所以如果上传的函数返回的是promise对象,则可以直接用Promise.all
函数即可得知所有图片上传完毕的回调。
事情的缘由:https://www.v2ex.com/t/275010#reply63
以前脑袋一热写了个教程网站:http://codeasily.net
回顾两年前的那篇帖子,以前那个时候学习前端的教程资源和对前端的认知比较守旧👨🌾,所以自己写教程👨💻,当时还有很多评论说做的挺好希望出多点教程,可惜毕业后一直忙于工作而且写教程消耗大量时间精力,就一直搁着了,非常感谢当时鼓励我的小伙伴👏。
现在翻看慕课网的一些课程,确实与时俱进多了一些前端真正该学的东西并且能提高行业竞争力的教程📚,出来工作 2 年认识的前端开发人员的整体姿势水平都提高了📈,也包括我自己😌。
这次做的这个编辑器只是为了完善两年前写的丑丑的 demo,改善一下界面和体验,还有今年写了个 web 端的 console 组件,用来填补这个编辑器的一个功能,也当做学习了。
我做这个编辑器不为发展成主业或者明星产品,市面上功能更强大的编辑器有很多,只是我喜欢动手体验学习,即使是轮子,只要能给我带来提升,都值得一试☝️
我是一个喜欢实践代替看教程的人,即使做的不怎么样,自己心里觉得满足就够了👌
]]>TypeScript 已经出来很久了,很多大公司很多大项目也都在使用它进行开发。上个月,我这边也正式跟进一个对集团的大型运维类项目。
项目要做的事情大致分为以下几个大模块
每一个模块要做的事情也很多,由于牵扯到公司业务,具体要做的一些事情这里我就不一一列举了,反正项目整体规模还是很大的。
在做了一些技术调研后,再结合项目之后的开发量级以及维护成本。最终我和同事在技术选型上得出一致结论,最终选型定为 Vue 最新全家桶 + TypeScript。
那么问题来了,为什么大型项目非得用 TypeScript 呢,ES6、7 不行么?
其实也没说不行,只不过我个人更倾向在一些协作开发的大型项目中使用 TypeScript 。下面我列一些我做完调研后自己的一些看法
1 | // 定义枚举 |
vue-slidePage is a fullscreen scrolling component of Vue.js, Based on slidePage
1 | $ npm i -S vue-slidepage |
Work on a Vue instance:
1 | <slide-container> |
1 | import { SlideContainer, SlidePage } from 'vue-slidePage' |
SlideContainer:
name | type | default | description |
---|---|---|---|
useAnimation | Boolean | true | 是否开启动画 |
refresh | Boolean | true | 每次滚动进入是否重新执行动画 |
useWheel | Boolean | true | 是否开启鼠标滚轮滑动 |
useSwipe | Boolean | true | 是否开启移动端触控滑动 |
SlideContainer:
name | description | $event |
---|---|---|
before | 每次全屏滚动前触发事件,回调三个参数(origin, direction, target),分别是滚动前的page序号、方向(‘next’/‘prev’)、滚动后的page序号 | $event.before |
after | 次全屏滚动后触发事件,回调三个参数(origin, direction, target),参数释义同上 | $event.after |
SlidePage:
name | description |
---|---|
default | Page’s content |
usage:1
2
3
4
5
6
7<slide-container>
<slide-page>
<img src="">
<p></p>
<button></button>
</slide-page>
</slide-container>
MIT
]]>这个react版本是依赖于slidePage的基础上用react实现的小插件,后续slidePage更新了,这个react-slidePage也会自动更新的哟。
react-slidePage is a fullscreen scrolling component of React, Based on slidePage
1 | $ npm i -S react-slidepage |
Work on a React instance:
1 | import { SlideContainer, SlidePage } from 'react-slidePage' |
SlideContainer:
name | type | default | description |
---|---|---|---|
page | Number | 1 | 初始页码 |
useAnimation | Boolean | true | 是否开启动画 |
refresh | Boolean | true | 每次滚动进入是否重新执行动画 |
useWheel | Boolean | true | 是否开启鼠标滚轮滑动 |
useSwipe | Boolean | true | 是否开启移动端触控滑动 |
SlideContainer:
name | description |
---|---|
before | 每次全屏滚动前触发事件,回调三个参数(origin, direction, target),分别是滚动前的page序号、方向(‘next’/‘prev’)、滚动后的page序号 |
after | 次全屏滚动后触发事件,回调三个参数(origin, direction, target),参数释义同上 |
MIT
]]>Demo:http://lipten.link/demo/editor
1.测试版,目前有个别功能不稳定。
1 | git clone https://github.com/lipten/Tiny-editor.git |
1 | <link rel="stylesheet" href="Tiny-editor.css"/> |
1 | <script src="http://cdn.bootcss.com/ace/1.2.3/ace.js" type="text/javascript" charset="utf-8"></script> |
1 | //--html结构比较复杂,看下载后的editor.html比较完整 |
1 | var Tiny = new TinyEditor() |
tab显示第几个面板,num传序号,从1开始
运行结果并跳到result面板
保存代码至缓存
全屏模式开启或关闭
dock边栏模式开启或关闭
实时运行模式,发生change事件时执行一次,仅开启dock模式的前提下才能开启
]]>slidePage现已推出3.0, 吸取了上一版本slidePage v2.1.1 的各种经验,弥补不足,与v2.1.1不同的是移除了对jquery和zepto的依赖,精简到只有一个js文件,优化了接口调用方式,更适合主流的前端框架,另外还有基于vue的插件版本vue-slidePage和基于react的插件版本react-slidePage
slidePage3 特别适合主流前端框架开发,无任何依赖库,源代码只有12kb,Gzip压缩后仅有2.4k, 接口符合插件具有的初始化、销毁、重载的方法,适配PC和移动端,具有单屏内容滚动、手动播放动画、动态重绘等特色功能,具体查看完整示例: fullFeatured
1 | // With npm |
1 | <link rel="stylesheet" type="text/css" href="slidePage.css"> |
1 | <div class="slide-container" id="slide-container"> |
您可以查看完整examples里的html文件结构 fullFeatured.html
1 | new slidePage() |
1 | var slidepage = new slidePage({ |
在slidePage中,page指的是每一次全屏滚动的一屏,也可以理解为每一屏对应的页码,必须是1以上的整数
name | type | default | description |
---|---|---|---|
slideContainer | String|Element | ‘.slide-container’ | 指定slidePage要运行的容器选择器或元素 |
slidePages | String|NodeList|HTMLCollection | ‘.slide-page’ | 指定slideContainer 容器里每个page的选择器或元素 |
page | Number | 1 | 首次进入的page页码 |
useAnimation | Boolean | true | 是否开启动画 |
refresh | Boolean | true | 每次滚动进入是否重新执行动画 |
useWheel | Boolean | true | 是否开启鼠标滚轮滑动 |
useSwipe | Boolean | true | 是否开启移动端触控滑动 |
name | description |
---|---|
before | 每次全屏滚动前触发事件,回调三个参数(origin, direction, target),分别是滚动前的page序号、方向(‘next’|’prev’)、滚动后的page序号 |
after | 每次全屏滚动后触发事件,回调三个参数(origin, direction, target),参数释义同上 |
为了方便示例用animate.css,动画效果可以自己实现
1 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.5.2/animate.min.css"> |
1 | <div class="step animated fadeIn" data-delay="1300"></div>; |
在想要动画控制的元素上加上step类,并加上css动画类名即可使用动画,data-delay属性控制动画延时播放(默认为100毫秒);
1 | <div class="lazy animated fadeIn"></div> |
滑动定位到下一屏
滑动定位到上一屏
传入page页码,滑动定位到对应的page
触发对应 page 的lazy手动动画
销毁当前实例,移除所有事件恢复class属性值。
当html里的page发生变化时需要执行动态更新。
newSlidePages
参数非必填,仅应对于初始化的时候slidePages
参数传入的是NodeList
或HTMLCollection
时才需要在更新的时候再传一次变化后的DOM结构通知更新。
> 此方法非常适合现在流行的数据驱动型框架,当模型数据驱动改变pege的排列时,执行update可以起到更新的作用,可以先看示例源码了解:custom.html
]]>