实现方案四:Flexbox

Flexbox 是非常适合实现这种效果的,使用 Flexbox
实现不仅不需要任何额外的元素,而且允许页脚的高度是可变的。

虽然大多数 Flexbox
布局常用于水平方向布局,但别忘了实际上它也可用于垂直布局,所以你需要做的是将垂直部分包装在一个
Flex 容器中,并选择要扩展的部分,他们将自动占用其容器中的所有可用空间。

CSS

html { height: 100%; } body { min-height: 100%; display: flex;
flex-direction: column; } .content { flex: 1; }

1
2
3
4
5
6
7
8
9
10
11
html {
    height: 100%;
}
body {
    min-height: 100%;
    display: flex;
    flex-direction: column;
}
.content {
    flex: 1;
}

需要注意的是想要兼容各种系统设备,需要兼顾 flex 的兼容写法。

记录一次利用 Timeline/Performance 工具进行 React 性能优化的真实案例

2017/07/10 · JavaScript
· React,
性能优化

原文出处: 波同学   

图片 1

  
设计图鉴赏推荐:别人家的设计作品

性能优化可以说是衡量一个react程序员的水平重要标准。

在学习react之初的时候,由于对react不够了解,因此写的项目虽然功能都实现了,但是性能优化方面的考虑却做得很少,因此回过头发现以前自己以前写的react代码确实有点糟糕。

为了提高自己的react水平,闲暇之余就把以前的老项目拿出来分析优化,看看都有哪些问题,以及如何优化。这里就以我以前做过的一个《投资日历》为例做一次优化记录。

项目线上地址:

优化工具timeline/performance基础使用教程:

chrome在版本57还是58的时候,将Timeline更名为performance

该项目主要的主要难点与性能瓶颈在于日历的左右滑动与切换。由于需求定制程度非常高,没有合适的第三方日历插件,所以就自己实现了一个。支持周日历与月日历的切换,支持左右滑动切换日期。

滑动效果仅支持移动端

问题出现在公司一款老的android测试机,发现动画效果非常卡顿。因此有了优化的必要。

嗨,送你一张Web性能优化地图

2018/07/23 · 基础技术 ·
性能优化

原文出处: Berwin   

我们都知道对于Web应用来说性能很重要。然而性能优化相关的知识却非常的庞大并且杂乱。对于性能优化需要做些什么以及性能瓶颈是什么,通常我们并不清楚。

不包括那些对性能优化有丰富经验的高手

事实上关于Web性能有很多可以优化的点,其中涉及到的知识大致可以划分为几类:度量标准编码优化静态资源优化交付优化构建优化性能监控

图片 2

图1. 性能优化分类

本文主要介绍性能优化需要做的事以及需要考虑的问题。目的在于给读者脑海中生成一个宏观的地图。

不会介绍每个优化项目具体如何操作。PS:后续会有系列文章针对不同优化分类下的具体优化操作进行更详细的介绍。

写在最后

以上几种实现方案,笔者都在项目中尝试过,每个实现的方法其实大同小异,同时也都有自己的利弊。
其中有的方案存在限制性问题,需要固定页脚高度;其中有的方案需要添加额外的元素或者需要
Hack 手段。同学们可以根据页面具体需求,选择最适合的方案。

当然,技术是不断更新的,也许还有很多不同的、更好的方案。但相信大家最终目都是一样的,为了更好的用户体验!

参考资料:

1 赞 4 收藏 1
评论

图片 3

利用工具定位问题

首先利用performance工具的的录制功能录制一段操作过程。
点击左上角的黑色原点开始录制。录制过程中,多次滑动周日历即可。然后大约5~10秒点击stop按钮停止录制。

录制结果如图。

图片 4

发现很多红帧,以及不正常的内存占用

从上图中我们可以发现以下问题:

1、
窗格中出现了红帧。出现红帧表示页面已经超负荷,会出现卡顿,响应缓慢等现象。
2、
大量的黄色区域,黄色区域越大,表示JavaScript的运行过程中的压力也越大。
3、
高额的内存占用,以及不正常的波动曲线(蓝色)。详细信息可以在上图中的JS Heap中查看。26.6 ~ 71.6M

图片 5

窗格图

我们可以在Main中观察到当前时刻的函数调用栈详情。当出现红帧,选中红帧区域,Main区域发现变化,变为当前选择时段的函数调用栈详情。我们会发现函数调用栈最上层有一个红色三角形。点击会在下面的Summary里发现对应的信息以及警告。如下图中的Warning: Recuring handler took 86.69 ms

图片 6

找到一个红点仔细观察,发现一个警告

4、
层级很高的函数调用栈。查看红色区域的函数调用栈,我们会发现大量的react组件方法被重复调用。

图片 7

7. 性能监控

最后,你可能需要一个性能检测工具来持续监视网站的性能。

实现方案一:absolute

通过绝对定位处理应该是常见的方案,只要使得页脚一直定位在主容器预留占位位置。

CSS

html, body { height: 100%; } .wrapper { position: relative; min-height:
100%; padding-bottom: 50px; box-sizing: border-box; } .footer {
position: absolute; bottom: 0; height: 50px; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
html, body {
    height: 100%;
}
.wrapper {
    position: relative;
    min-height: 100%;
    padding-bottom: 50px;
    box-sizing: border-box;
}
.footer {
    position: absolute;
    bottom: 0;
    height: 50px;
}

这个方案需指定 html、body 100% 的高度,且 content 的 padding-bottom
需要与 footer 的 height 一致。

一步一步开始优化

从上面的分析就可以简单看出,虽然实现了非常复杂的功能,看上去很厉害的样子,其实内部非常糟糕。几乎可以作为react用法的反面教材了。

优化分析1

在上面的函数调用栈中,我们发现有一个方法出现的次数非常多,那就是receiveComponent。因此可以预想到某个组件里肯定使用了receiveComponent相关的生命周期的方法。检查代码,确实发现了几处componentWillReceiveProps的使用。

<span class=”hljs-comment”>//
每一次更新状态都会刷新一次,导致了大量的计算</span> <span
class=”hljs-selector-tag”>componentWillReceiveProps</span>(nextProps)
{ <span class=”hljs-selector-tag”>this</span><span
class=”hljs-selector-class”>.setState</span>({ <span
class=”hljs-attribute”>navProcess</span>:
getNavigation(nextProps.currentData) }) }

1
2
3
4
5
6
<span class="hljs-comment">// 每一次更新状态都会刷新一次,导致了大量的计算</span>
<span class="hljs-selector-tag">componentWillReceiveProps</span>(nextProps) {
    <span class="hljs-selector-tag">this</span><span class="hljs-selector-class">.setState</span>({
        <span class="hljs-attribute">navProcess</span>: getNavigation(nextProps.currentData)
    })
}

刚开始学习react时可能会认为生命周期是一个学习难点,我们不知道什么情况下去使用它们。慢慢的随着经验的增加,才发现,生命周期方法是万万不能轻易使用的。特别是与props/state改变,与组件重新渲染相关的几个生命周期,如componentWillReceiveProps
shouldComponentUpdatecomponentWillUpdate等。这个实际案例告诉我们,他们的使用,会造成高额的性能消耗。所以不到万不得已,不要轻易使用他们。

曾经看到过一篇英文博文,分析的是宁愿多几次render,也不要使用shouldComponentUpdate来优化代码。但是文章地址找不到,如果有其他看过的朋友请在评论里留言分享一下,感谢

而只有componentDidMount是非常常用的。

上面几行简单的代码,却暴露了一个非常恐怖的问题。一个是使用了生命周期componentWillReceiveProps。而另一个则是在props改变的同时,还修改了组件的state。我们知道当props在父级被改变时会造成组件的重新渲染,而组件内部的state的改变同样也会造成组件的重新渲染,因此这几句简单的代码,让组件的渲染无形中发生了很多次。

因此优化的方向就朝这两个方向努力。首先不能使用componentWillReceiveProps,其次我发现navProcess其实可以在父级组件中计算,并通过props传递下来。所以优化后的代码如下:

function Index(props) { const { currentD, currentM, selectD, setDate,
loading, error, process, navProcess } = props; return ( <div
className=”main”> <Calendar selectDate={selectD}
curDate={currentD} curMonth={currentM} setDate={setDate} /> { loading
? null : error ? <ErrorMessage queryData={process.bind(null,
selectD)} /> : <Classification navProcess={navProcess}
selectDate={selectD} /> } {loading ? <Loading isLoading={ loading
} /> : null} </div> ) }

1
2
3
4
5
6
7
8
9
10
function Index(props) {
    const { currentD, currentM, selectD, setDate, loading, error, process, navProcess } = props;
    return (
        <div className="main">
            <Calendar selectDate={selectD} curDate={currentD} curMonth={currentM} setDate={setDate} />
            { loading ? null : error ? <ErrorMessage queryData={process.bind(null, selectD)} /> : <Classification navProcess={navProcess} selectDate={selectD} /> }
            {loading ? <Loading isLoading={ loading } /> : null}
        </div>
    )
}

意外的惊喜是发现该组件最终优化成为了一个无状态组件,轻装上阵,完美。

这样优化之后,重新渲染的发生少了好几倍,运行压力自然减少很多。因此当滑动周日历时已经不会有红帧发生了。但是月日历由于DOM节点更多,仍然存在问题,因此核心的问题还不在这里。我们还得继续观察。

优化分析2

在函数调用栈中我们可以很明显的看到ani方法。而这个方法是我自己写的运动实现。因此我得重点关注它的实现中是不是存在什么问题。仔细浏览一遍,果然有问题。

发现在ani方法的回调中,调用了2次setDate方法。

// 导致顶层高阶组件多一次渲染,下层多很多次渲染 setDate(newCur, 0);
setDate({ year: newCur.year, month: newCur.month }, 1)

1
2
3
// 导致顶层高阶组件多一次渲染,下层多很多次渲染
setDate(newCur, 0);
setDate({ year: newCur.year, month: newCur.month }, 1)

该setDate方法是在父级中定义用来修改父级state的方法。他的每一次调用都会引发由上自下的重新渲染,因此多次调用的代价是非常大的。所以我将要面临的优化就是想办法将这两次调用合并为一次。

先看看优化以前setDate方法的定义是如何实现的。我想要通过不同的number来修改不同的state属性。但是没有考虑如果需要修改多个呢?

setDate = (date, number) => { if (number == 0) { this.setState({
currentD: date, currentM: { year: date.year, month: date.month } }) } if
(number == 1) { this.setState({ currentM: date }) } if (number == 2) {
_date = date; _month = { year: date.year, month: date.month };
this.setState({ currentD: _date, currentM: _month, selectD: _date })
this.process(date); } }

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
setDate = (date, number) => {
            if (number == 0) {
                this.setState({
                    currentD: date,
                    currentM: { year: date.year, month: date.month }
                })
            }
 
            if (number == 1) {
                this.setState({
                    currentM: date
                })
            }
 
            if (number == 2) {
                _date = date;
                _month = { year: date.year, month: date.month };
                this.setState({
                    currentD: _date,
                    currentM: _month,
                    selectD: _date
                })
                this.process(date);
            }
        }

修改该方法为,传递一个对象字面量进去进行修改

setDate = (options) => { const state = { …this.state, …options };
if (options.selectD) { _date = options.selectD; _month = { year:
_date.year, month: _date.month } state.currentD = _date;
state.currentM = _month; this.process(_date, state); } else {
this.setState(state); } }

1
2
3
4
5
6
7
8
9
10
11
12
setDate = (options) => {
    const state = { …this.state, …options };
    if (options.selectD) {
        _date = options.selectD;
        _month = { year: _date.year, month: _date.month }
        state.currentD = _date;
        state.currentM = _month;
        this.process(_date, state);
    } else {
        this.setState(state);
    }
}

该方法有两处优化,第一处优化是传入的参数调整,想要修改那一个就直接传入,用法类似setState。第二处优化是在this.process方法中只调用一次this.setState,总之这样处理的目的都是统一的,当想要数据修改时只发生一次渲染。而之前的方法会导致3次甚至多次渲染。这样优化之后,性能自然会提升很多。

优化分析3

但是优化并没有结束,因为再录制一段查看,仍然会发现红帧出现。
进一步查看Calendar组件,发现每一次滑动切换,都会发生4次渲染。肯定有问题。

我的目的是最多发生两次无法避免的渲染。多余的肯定是因为代码的问题导致的冗余渲染。因此继续查看代码。

发现在递归调用ani方法时,this.timer并没有被及时取消。

//
我的目的是每一次递归会调用一次requestAnimationFrame与cancelAnimationFrame
// 但是这样写只会在递归结束时调用一次cancelAnimationFrame if (offset ==
duration) { callback && callback(); cancelAnimationFrame(this.timer); }
else { this.timer = requestAnimationFrame(ani); }

1
2
3
4
5
6
7
8
// 我的目的是每一次递归会调用一次requestAnimationFrame与cancelAnimationFrame
// 但是这样写只会在递归结束时调用一次cancelAnimationFrame
if (offset == duration) {
    callback && callback();
    cancelAnimationFrame(this.timer);
} else {
    this.timer = requestAnimationFrame(ani);
}

因此修改如下:

ani = () => { …. if (offset == duration) { callback && callback();
} else { this.timer = requestAnimationFrame(ani); }
cancelAnimationFrame(this.timer); }

1
2
3
4
5
6
7
8
9
ani = () => {
    ….
    if (offset == duration) {
        callback && callback();
    } else {
        this.timer = requestAnimationFrame(ani);
    }
    cancelAnimationFrame(this.timer);
}

这样优化之后,发现内存占用下降一些,但是红帧仍然存在。看来计算量并没有下降。继续优化。

优化分析4

发现Calendar组件中,根据props中的curDate,curMonth计算而来的weekInfo与monthInfo被写在了该组件的state中。由于state中数据的变化都会导致重新渲染,而我发现在代码中有多处对他们进行修改。

componentDidMount() { const { curDate, curMonth } = this.props
this.setState({ weekInfo: calendar.get3WeekInfo(curDate), monthInfo:
calendar.get3MonthInfo(curMonth) }) this.setMessageType(curDate, 0);
this.setMessageType(curMonth, 1); }

1
2
3
4
5
6
7
8
9
10
11
componentDidMount() {
    const { curDate, curMonth } = this.props
 
    this.setState({
        weekInfo: calendar.get3WeekInfo(curDate),
        monthInfo: calendar.get3MonthInfo(curMonth)
    })
 
    this.setMessageType(curDate, 0);
    this.setMessageType(curMonth, 1);
}

其实这种根据props中的参数计算而来的数据是万万不能写在state中的,因为props数据的变化也会导致组件刷新重新渲染,因此一个数据变化就会导致不可控制的多次渲染。这个时候更好的方式是直接在render中计算。因此优化如下:

ender() { … let info = type == 0 ? c.get3WeekInfo(curDate) :
c.get3MonthInfo(curMonth); … }

1
2
3
4
5
ender() {
    …
    let info = type == 0 ? c.get3WeekInfo(curDate) : c.get3MonthInfo(curMonth);
    …
}

优化结果如下图:

图片 8

image.png

与第一张图对比,我们发现,运动过程中出现的红帧没有了。二是窗格中黄色区域大量减少,表示js的计算量减少很多。三是内存占用大幅降低,从最高的71M减少到了33M。内存的增长也更加平滑。

后续的优化大致目的都是一样。不再赘述。

总结一下:

  1. 尽量避免生命周期方法的使用,特别是与状态更新相关的生命周期,使用时一定要慎重。
  2. 能通过props重新渲染组件,就不要在额外添加state来增加渲染压力。
  3. 一切的优化方向就是在实现功能的前提下减少重新渲染的发生。

这其中涉及到的技巧就需要大家在实战中慢慢掌握了。

1 赞 收藏
评论

图片 3

5.5 使用HTTP缓存头

正确设置expirescache-control和其他HTTP缓存头。

推荐使用Cache-control: immutable避免重新验证。

实现方案三:table

通过 table 属性使得页面以表格的形态呈现。

CSS

html, body { height: 100%; } .wrapper { display: table; width: 100%;
min-height: 100%; } .content { display: table-row; height: 100%; }

1
2
3
4
5
6
7
8
9
10
11
12
html, body {
    height: 100%;
}
.wrapper {
    display: table;
    width: 100%;
    min-height: 100%;
}
.content {
    display: table-row;
    height: 100%;
}

需要注意的是,使用 table 方案存在一个比较常见的样式限制,通常
margin、padding、border 等属性会不符合预期。
笔者不建议使用这个方案。当然,问题也是可以解决的:别把其他样式写在 table
上。

1.2 设定目标

  • 100毫秒的界面响应时间与60FPS
  • 速度指标(Speed Index)小于1250ms
  • 3G网络环境下可交互时间小于5s
  • 重要文件的大小预算小于170kb

以上四种指标的设定都有据可循。详细信息请查看RAIL性能模型。

如何实现

假设我们页面的 HTML 结构是这样:

XHTML

<div class=”wrapper”> <div class=”content”><!–
页面主体内容区域 –></div> <div class=”footer”><!–
需要做到 Sticky Footer 效果的页脚 –></div> </div>

1
2
3
4
<div class="wrapper">
    <div class="content"><!– 页面主体内容区域 –></div>
    <div class="footer"><!– 需要做到 Sticky Footer 效果的页脚 –></div>
</div>

5.2 使用 Tree-shaking、Scope hoisting、Code-splitting

Tree-shaking是一种在构建过程中清除无用代码的技术。使用Tree-shaking可以减少构建后文件的体积。

目前Webpack与Rollup都支持Scope Hoisting。它们可以检查import链,并尽可能的将散乱的模块放到一个函数中,前提是不能造成代码冗余。所以只有被引用了一次的模块才会被合并。使用Scope Hoisting可以让代码体积更小并且可以降低代码在运行时的内存开销,同时它的运行速度更快。前面2.1节介绍了变量从局部作用域到全局作用域的搜索过程越长执行速度越慢,Scope Hoisting可以减少搜索时间。

code-splitting是Webpack中最引人注目的特性之一。此特性能够把代码分离到不同的bundle中,然后可以按需加载或并行加载这些文件。code-splitting可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

Sticky Footer,完美的绝对底部

2017/04/14 · CSS · 1
评论 ·
footer

原文出处:
凹凸实验室   

3. 静态资源优化

Web应用的运行离不开静态资源,所以对静态资源的优化至关重要。

写在前面

做过网页开发的同学想必都遇到过这样尴尬的排版问题:
在主体内容不足够多或者未完全加载出来之前,就会导致出现(图一)的这种情况,原因是因为没有足够的垂直空间使得页脚推到浏览器窗口最底部。但是,我们期望的效果是页脚应该一直处于页面最底部(如图二):

图片 10

笔者最近在项目中也遇到过这样的场景,在寻找最佳解决方案的过程中,了解到了
“Sticky Footer” 这个名词。
本文将带大家重新认识这个常见的网页效果,以及一些可行的实现方案。

6. 其他

其他一些值得考虑的优化点:

  • HTTP2
  • 使用最高级的CDN(付费的比免费的强的多)
  • 优化字体
  • 其他垂直领域的性能优化

实现方案二:calc

通过计算函数 calc 计算(视窗高度 –
页脚高度)赋予内容区最小高度,不需要任何额外样式处理,代码量最少、最简单。

CSS

.content { min-height: calc(100vh – 50px); } .footer { height: 50px; }

1
2
3
4
5
6
.content {
    min-height: calc(100vh – 50px);
}
.footer {
    height: 50px;
}

如果不需考虑 calc() 以及 vh
单位的兼容情况,这是个很理想的实现方案。
同样的问题是 footer 的高度值需要与 content 其中的计算值一致。

3.1 使用BrotliZopfli进行纯文本压缩

在最高级别的压缩下Brotli会非常慢(但较慢的压缩最终会得到更高的压缩率)以至于服务器在等待动态资源压缩的时间会抵消掉高压缩率带来的好处,但它非常适合静态文件压缩,因为它的解压速度很快。

使用Zopfli压缩可以比Zlib的最大压缩提升3%至8%。

什么是 “Sticky Footer”

所谓 “Sticky
Footer”,并不是什么新的前端概念和技术,它指的就是一种网页效果:
如果页面内容不足够长时,页脚固定在浏览器窗口的底部;如果内容足够长时,页脚固定在页面的最底部。
总而言之,就是页脚一直处于最底,效果大致如图所示:

图片 11

当然,实现这种效果的方法有很多种,其中有通过脚本计算的,有通过 CSS
处理的,脚本计算的方案我们不在本文探讨。
下面我们看看有哪些通过 CSS
可以实现且适用于移动端开发的方案,并分析其中的利弊。

4. 交付优化

交付优化指的是对页面加载资源以及用户与网页之间的交付过程进行优化。

2.2 DOM

应用在运行时,性能的瓶颈主要在于DOM操作的代价非常昂贵,下面列出一些关于DOM操作相关提升性能的建议:

  • 在JS中对DOM进行访问的代价非常高。请尽可能减少访问DOM的次数(建议缓存DOM属性和元素、把DOM集合的长度缓存到变量中并在迭代中使用。读变量比读DOM的速度要快很多。)
  • 重排与重绘的代价非常昂贵。如果操作需要进行多次重排与重绘,建议先让元素脱离文档流,处理完毕后再让元素回归文档流,这样浏览器只会进行两次重排与重绘(脱离时和回归时)。
  • 善于使用事件委托

4.4.3 prefetch

Prefetch用于标识下一个导航可能需要的资源。浏览器会获取该资源,一旦将来请求该资源,浏览器可以提供更快的响应。

<link rel=”prefetch” href=”//example.com/next-page.html” as=”html”
crossorigin=”use-credentials”> <link rel=”prefetch”
href=”/library.js” as=”script”>

1
2
<link rel="prefetch" href="//example.com/next-page.html" as="html" crossorigin="use-credentials">
<link rel="prefetch" href="/library.js" as="script">

浏览器不会预处理、不会自动执行、不会将其应用于当前上下文。

ascrossorigin选项都是可选的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注