博客恢复了

今年年中,本博所在的阿里云服务器忘记续费,导致程序和源码被清空删除了,写了9年的博客就这样没了,甚是可惜。咨询了在阿里云的同事,均表示未续费的服务器被格式化后数据是无法找回的。无奈只能通过其他方式将散落在网络上的各种转载博客的文章重新收集起来。9月开始断断续续的进行博客恢复之旅,目前部分文章已恢复,还有一些没法恢复的就算了。

记录一下恢复的过程:

从备份中恢复

依稀记得,在很久以前做WordPress的时候,曾经备份过一次博客的数据库到邮箱里,于是在qq邮箱里搜关键字把博客的数据库搜出来了,里面有70多篇文章,但没法直接恢复,因为数据库太旧了,最新的WordPress修改了一些字段并新增了一些关联表。

于是先在阿里云重新购买并初始化一个Nginx + php 环境,搭个WordPress + phpmyadmin 上去。

搭建完之后,将旧数据库的wp_postswp_commentswp_term_relationshipswp_term_taxonomywp_terms等几个核心表的数据拼成新库所需的sql导入到WordPress。至此12年之前的文章算是恢复了,但回头看这些文章,实际参考意义不大了,前端技术变化太快,那个时候还是在讨论jquery、讨论ie6、7的兼容性问题,权当是纪念自己的职业生涯吧。

从网络搜集

虽然博客更新频率也不算太高,保持一个月一篇的速度,但这几年下来也有几十篇了。先根据邮箱搜索一下博客里以前的评论(因为博客文章有评论的话,会邮件通知)。

从评论的邮件里可以翻出博客的文章标题,基于标题再去百度上搜索对应的文章,所幸博客还是有蛮多人关注的,网上也有一些人转载了我的文章,这个时候发挥百度和google的作用了。

经过一番搜索,发现有几个网站还专门爬了我的博客,比如这个:

http://ju.outofmemory.cn/feed/2494/

把我15年和16年的博客每篇文章给转载了,真是庆幸,于是从这个网站上把相关的3页文章给再次搬回网站,文章发表时间根据该网站转载的时间,稍微调前一点(毕竟是原创,总不能比转载的时间更晚是吧)。

那么剩下17年和12到15年这几年的文章了,只能一篇一篇的去找了,也再找回几篇质量比较高的,剩下一些因为标题太普遍了,搜不到与我发表的文章内容相关的。

感叹

经过这件事情,说明数据备份是多么重要啊。也说明自己懒了,很久定的计划,一个月至少一篇,但是近2年没有坚持下来,后面还要坚持更新下去。

这次博客重新恢复,虽然损失了很多网友的评论和一些无法恢复的文章,但大部分也算找回了。以前的文章代表过去,后续继续产出高质量文章,加油!

requestAnimationFrame最佳实践

requestAnimationFrame ,这个方法应该都不陌生,字面含义是请求动画帧。从API命名来看,和动画有着密切的关系。其用法跟setTimeout差不多,与setTimeout相比,最大的优势是 由浏览器来决定函数的执行时机 。形象一点的解释就是:告诉浏览器说 “我这里有一个函数要执行,你有空了帮忙执行一下”,然后浏览器相对比较空闲的时候就给执行了。

用法一:动画

raf api本身的设计就是用来解决js动画的性能问题。那么,为什么raf做动画性能会更好呢?主要原因在于 raf更加智能,它并非加快执行速度,而是适当时候降帧,防止并解决丢帧问题 。当它发现无法维持60fps的频率时,它会把频率降低到30fps来保持帧数的稳定。也就是说如果上一次raf的回调执行时间过长,那么触发下一次raf回调的时间就会缩短,反之亦然,这也是为什么说由浏览器来决定执行时机性能会更好。

(function animloop(){
  window.requestAnimFrame(animloop);
  render();
})();

DEMO:

See the Pen bErOLz by hugo ( @baofen14787 ) on CodePen .

用法二: 函数节流

在高频率事件中,为了防止16ms内发生多次函数执行,使用raf可保证16ms内只触发一次,这既能保证流畅性也能更好的节省函数执行的开销。 16ms内函数执行多次没有意义,因为显示器16ms刷新一次,多次执行并不会在界面上有任何显示。

举个例子:

var $box = $('#J_num2'),
      $point = $box.find('i');
$box.on('mousemove',function(e){
  requestAnimationFrame(function(){
      $point.css({
          top : e.pageY,
          left : e.pageX
      })
  })

})

用法三:CPU节能

raf的另一个特性是:如果页面不是激活状态下的话,函数会自动暂停,有效节省了CPU开销。在移动端,如果页面中有自动播放的轮播图、倒计时或使用 setTimeout/setInterval 来执行任务的定时器。那么 当app进到后台或是锁屏后,webviewcorethread仍然持续占用CPU,导致耗电 。而使用raf可以很简单的解决此类问题。

PS:复杂情况下,可配合 window.onblur & window.focus 、 document.onvisibilitychange 、 document.onpause &document.onresume 等相关的方法进行实现,但这篇文章里不做深入探讨。

(function(){
        var timer;
        var $txt = $('#J_num2'),
                num = 0;
        function play(){
            timer = setTimeout(function(){
                  //使用raf实现非激活状态下不运行
                requestAnimationFrame(function(){
                    stop();
                    next();
                });
            },1000)
        }

        function stop(){
            clearTimeout(timer)
        }

        function next(){
            $txt.text(num++);
            play();
        }
        play();
    })();

DEMO:

See the Pen OMxodB by hugo ( @baofen14787 ) on CodePen .

用法四:分帧初始化

都知道,raf的执行时间约为16.7ms,即为一帧。那么可以使用raf将页面初始化的函数进行打散到每一帧里,这样 可以在初始化时降低CPU及内存开销 

很多页面,初始化加载时,CPU都会有很明显的波动,就是因为大量的操作都集中到了一点上。

举个例子:

页面中有4个模块,A、B、C、D,在页面加载时进行实例化,一般的写法类似于:

$(function(){
    new A();
    new B();
    new C();
    new D();
})

而使用raf可将每个模块分别初始化,即 每个模块都有16ms的初始化时间

    $(function(){
        var lazyLoadList = [A,B,C,D];
        $.each(lazyloadList, function(index, module){
        window.requestAnimationFrame(function(){new module()});
        }); 
        
    })

用法五:异步化

raf实际是一种异步化的操作,曾经 setTimeout(function(){},0) 一度成为解决了很多前端疑难杂症的法宝。而现在,可以用raf来代替。

Chrome渲染分析之Rendering工具使用(3)

在前面2篇文章中介绍了 Show paint rectangles 和 show composited layer borders 两个选项的作用。今天接着讲下一个: show FPS meter

show FPS meter

show fps meter 可以理解为显示FPS帧频/帧数。开启这个选项后,右上角会实时显示当前页面的FPS。

先简单科普一下啥是FPS。FPS全称叫 Frames Per Second (每秒帧数)。帧数越高,动画显示的越流畅。一般的液晶显示器的刷新频率也就是 60HZ。也就是说, 要想页面上的交互效果及动画流畅。那么FPS稳定在60左右,是最佳的体验。 。据悉 ios上的交互效果都是60FPS呢。

记得以前做Flash游戏的时候,FPS帧数是游戏流畅度的一个重要指标。在web端,道理也是一样。

还记得我之前的文章提到 《web移动端性能调优及16ms优化》 这里的16毫秒,实际就是 1000ms/60FPS = 16.6ms。 也就是一帧所花费的时间约是16毫秒。

科普完毕,回到正题。chrome提供的 show FPS meter 选项,在我们制作测试页面交互及动画性能时非常有用。同时它也提供了当前页面的GPU占有率给我们。

React Native 自定义事件机制

React Native 刚出来不久,写过一篇 react-native 组件间通信 的文章,里面介绍了组件间三种关系的相互通信。用了一段时间下来,感觉React Native 这套组件间传递数据的方法,实施起来特别麻烦,并且不利于解耦。

翻看React Native的文档,并没有看到实现组件间消息机制的方法。但在源码里,找到了相关的实现。

其中 EventEmitter 实现了事件机制,基于EventEmitter可以实现在一个component里注册一个自定义事件,在另一个component监听该事件,从而实现简单的 观察者模式 

RCTDeviceEventEmitter 从源码里可以看到,只是实例化了一个 EventEmitter 而已。在 PushNotificationIOS 组件中有相关的使用。

而 Subscribable 也是对 EventEmitter 做了一层封装,实现了一个较为安全的 mixin 方法来处理事件。

具体API请直接查看源码,比较简单,就不一一介绍了。

DEMO3

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');

var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');

var {
  StyleSheet,
  Text,
  View,
  TextInput
} = React;


var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});


var Input = React.createClass({

    handleUpdateChange(text) {
        RCTDeviceEventEmitter.emit('change',text);
    },


  render() {

    return (
      <View style={{justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF'}}>
        <TextInput onChangeText={(text) => this.handleUpdateChange(text)} style={{ width : 200, height: 40, borderColor: 'gray', borderWidth: 1}} />
      </View>
    );
  }
});




var ShowText = React.createClass({

    getInitialState(){
      return {
        text : ''
      }
    },

    componentDidMount(){
      var me = this;
      RCTDeviceEventEmitter.addListener('change',function(text){
         me.setState({
          text : text
         })
      })
    },


    render() {
        return (
          <View style={{justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF'}}>
            <Text>{this.state.text}</Text>
          </View>
        );
    }
});

module.exports = React.createClass({


  render() {
    return (
      <View style={styles.container}>
        <Input />
        <ShowText />
      </View>
    );
  }
});

另一个使用Subscribable实现的DEMO: https://github.com/colinramsay/react-native-event-emitters/blob/master/index.ios.js

Chrome渲染分析之Rendering工具使用(2)

距离这个专题的上一篇 《Chrome渲染分析之Rendering工具使用(1)》 已经隔了一年多了,迟迟没有下笔,囧!

2.show composited layer borders

中文可翻译为:显示层的组合边界。

我们知道,在页面最终是由多个“图层”渲染而成。勾上这个选项,页面上的“layer(层)”会加上一个黄色的边框显示出来,如下图的 天猫首页 头部所示:

其中:

  • 黄色边框:用于显示页面上的layer
  • 蓝色栅格线:表示的是分块,这些分块可以看作是比层更低一级的单位

当然,还有其他颜色的边框线,比如图片如果单独有个layer的话,边框线是 蓝色的 

使用这个工具,可以查看当前页面的layer情况,更好的发现页面不需要的layer将之清除。

layer存在的意义

在弄明白这个问题之前,我们需要先了解一个dom元素最终是如何转变为我们屏幕上可视的图像。在概念上讲,可简单的分为四个步骤:

  1. 获取 DOM 并将其分割为多个层
  2. 将每个层独立的绘制进位图中
  3. 将层作为纹理上传至 GPU
  4. 复合多个层来生成最终的屏幕图像。

可以将这个过程理解为设计师的Photoshop文件。在ps源文件里,一个图像是由若干个图层相互叠加而展示出来的。分成多个图层的好处就是每个图层相对独立,修改方便,对单个图层的修改不会影响到页面上的其他图层。

基于photoshop的图层理念来理解web端的层,那么就很容易理解了。layer存在的意义在于: 用最小的代价来改变某个页面元素 

我们可以将某个css动画或某个js交互效果把它抽离到一个单独的渲染层,这样可以加快渲染的效率。

如何创建layer

  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 <video> 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染),关于这点,更详细的实践可查看 该文章

在webkit内核的浏览器中,如果有上述情况,则会创建一个独立的layer。

其中第一点是最常用的手段,比如我们有时候给一个css效果加上 transform: translateZ(0); ,目的就是为了创建一个独立的layer。

另外还有另外一个css属性: will-change 也能实现同样的效果。

layer 过多带来的问题

还是拿photoshop来做比喻,一个ps文件如果有非常多的图层,那么这个文件肯定是非常大的。那对于web端也是一样,创建一个新的渲染层,它得消耗额外的内存和管理资源。当在内存资源有限的设备,比如手机上,由于过多的渲染层来带的开销而对页面渲染性能产生的影响,甚至远远超过了它在性能改善上带来的好处。

举个栗子,我们在天猫首页上加入css: * {-webkit-transform: translateZ(0);} 。 然后使用 timeline 可以看到,天猫的渲染耗时非常严重。

其影响的是页面渲染的最后一个环节: Composite Layers 

那么,一个合理的策略是: 当且仅当需要的时候才为元素创建渲染层。

更直观的查看页面layer

除了rendering 里提供的 show composited layer borders 选项外,还有一个更为直观的3d图像展示:

先选中 timeline 的某一帧,然后选择下面的 layer 标签tab,在右侧的区域就可以看到整个页面的3d图层了。

在这个视图中,你可以对这一帧中的所有渲染层进行扫描、缩放等操作,同时还能看到每个渲染层被创建的原因。

扩展阅读

若需要更细致的资料,可以查看下面这些文章。(可能需要翻墙)

wordpress 使用七牛镜像插件与SyntaxHighlighter插件冲突的解决方法

今天本博的阿里云服务器过期了。早上起来续费,发现要2k大洋。想想心疼啊,博客日访问量不超200UV,搞个大宽带,着实浪费。

续费时,就选了流量按需收费,0.8元/G。为了节省带宽,想到了用七牛云存储来保存及加速图片。

于是,在七牛上注册了账户,并安装了水煮鱼的 七牛镜像存储插件

整个过程一切顺利,图片成功转换为七牛图片了。但发现文章里的代码片段无法显示了。而我使用的是 SyntaxHighlighter 插件来高亮代码,于是我判断应该是 七牛镜像存储插件 与 SyntaxHighlighter插件 冲突。

google了一番,发现没有解决方案,莫非同时安装这2个插件的人太少?泥煤啊。

于是,只有放弃使用七牛镜像插件了。看了下七牛加速的原理,实际就是一个url替换,比如我的这张图片: http://www.ghugo.com/wp-content/uploads/2015/08/52.png 只需要替换域名后就可以直接使用 http://7qnca0.com1.z0.glb.clouddn.com/wp-content/uploads/2015/08/52.png

那么简单的一个功能,也不需要专门使用插件来处理了,直接写代码替换下即可。于是继续google一下,找到一段代码,将如下代码贴到你主题里的 functions.php 文件最末尾即可。

if ( !is_admin() ) {
    add_action('wp_loaded','c7sky_ob_start');
    
    function c7sky_ob_start() {
        ob_start('c7sky_qiniu_cdn_replace');
    }
    
    //修改自七牛镜像存储 WordPress 插件
function c7sky_qiniu_cdn_replace($html){
    $local_host = 'http://www.ghugo.com'; //博客域名
    $qiniu_host = 'http://7qnca0.com1.z0.glb.clouddn.com'; //七牛域名
    $cdn_exts   = 'css|png|jpg|jpeg|gif|ico'; //扩展名(使用|分隔)
    $cdn_dirs   = 'wp-content|wp-includes'; //目录(使用|分隔)
    
    $cdn_dirs   = str_replace('-', '\-', $cdn_dirs);

    if ($cdn_dirs) {
        $regex  =  '/' . str_replace('/', '\/', $local_host) . '\/((' . $cdn_dirs . ')\/[^\s\?\\\'\"\;\>\<]{1,}.(' . $cdn_exts . '))([\"\\\'\s\?]{1})/'; $html = preg_replace($regex, $qiniu_host . '/$1$4', $html); } else { $regex = '/' . str_replace('/', '\/', $local_host) . '\/([^\s\?\\\'\"\;\>\<]{1,}.(' . $cdn_exts . '))([\"\\\'\s\?]{1})/';
        $html =  preg_replace($regex, $qiniu_host . '/$1$3', $html);
    }
    return $html;
}
}

七牛云加速就是那么简单!

React Native ListView sticky效果实现

React Native中,ScrollView组件可以使用 stickyHeaderIndices 轻松实现 sticky 效果。

而使用ListView组件时,使用 stickyHeaderIndices 则不生效。

在IOS中的ListView的内部结构,实际是由多个Section组成,最典型的案例就是iphone手机的通讯录,滚动时每个section header会吸顶。

而在web端,使用 position : -weblit-sticky 实现的吸顶效果,也是类似的原理。具体可以看下之前的文章: 《position:sticky 使用条件分析》 .

好了,废话不多。在ListView中实现 sticky ,需要使用 cloneWithRowsAndSections 方法,将 dataBlob (object), sectionIDs (array),rowIDs (array) 三个值传进去即可。

dataBlob

dataBlob 包含ListView所需的所有数据(section header 和 rows),在ListView渲染数据时,使用 getSectionData 和 getRowData 来渲染每一行数据。 dataBlob 的 key 值包含 sectionID rowId

sectionIDs

sectionIDs 用于标识每组section。

rowIDs

rowIDs 用于描述每个 section 里的每行数据的位置及是否需要渲染。在ListView渲染时,会先遍历 rowIDs 获取到对应的 dataBlob 数据。

根据上面3个数据的定义,模拟出对应的数据结构如下:

var dataBlob = {
     'sectionID1' : { ...section1 data },
     'sectionID1:rowID1' : { ...row1 data },
     'sectionID1:rowID2' : { ..row2 data },
     'sectionID2' : { ...section2 data },
     'sectionID2:rowID1' : { ...row1 data },
     'sectionID2:rowID2' : { ..row2 data },
     ...
}

var sectionIDs = [ 'sectionID1', 'sectionID2', ... ]

var rowIDs = [ [ 'rowID1', 'rowID2' ], [ 'rowID1', 'rowID2' ], ... ]

在 DataSource 中,告诉ListView获取row和section的方法。

var getSectionData = (dataBlob, sectionID) => {
      return dataBlob[sectionID];
 }
var getRowData = (dataBlob, sectionID, rowID) => {
      return dataBlob[sectionID + ':' + rowID];
}
this.ds = new ListView.DataSource({
      getSectionData: getSectionData,
      getRowData: getRowData,
      rowHasChanged: (r1, r2) => r1 !== r2,
      sectionHeaderHasChanged: (s1, s2) => s1 !== s2
})

最后将数据传进ListView

 this.dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs) 

最终效果如图:

1

完整代码示例可查看: https://github.com/hugohua/rn-listview-example

参考文章:

http://moduscreate.com/react-native-listview-with-section-headers/

React Native 之flex布局

react native 的flex布局,是web的阉割版本,目前还不支持 flex-shrink 、 flex-basis 、 order 几种flex属性,同时现有支持的flex属性值也不全。

alignItems

调整伸缩项目在侧轴上的定位方式

可选值: flex-start flex-end center stretch

alignSelf

alignSelf 属性会覆盖容器的 alignItems 属性,取值和用法 alignItems 一样。

可选值: auto flex-start flex-end center stretch

justifyContent

与 alignItems 相呼应,表示元素在主轴(横轴)方向上的对齐方式

可选值: flex-start flex-end center space-between space-around

flexDirection

子元素在父容器中的排列位置,相比于web,少了 row-reverse 和 column-reverse 两个值

可选值: row column

flexWrap

子元素超出父容器时是否换行

可选值: wrap nowrap

本文参考至:

react-native 组件间通信

react native 中,组件间通信无非3种情况:

以DEMO为例,有2个组件Input和 ShowText 。

1、组件嵌套,即Input是ShowText的子组件,在组件间通信可以通过state进行。
2、组件为同级关系,即Input和ShowText都属于页面级别内的组件,这是常见的组件间通信,可各自提供接口进行通信,利用props进行
3、除了以上2种的关系外的组件,比如Input在一个Container组件内,页面级别只有2个组件Container和ShowText。这种情况下,可创建一个全局的通信类。在react-native的2014 example 中 都有这样的代码,这里就不再贴代码了。

源码可查看github:https://github.com/baofen14787/react-native-communicate-demo

第一种情况:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');


var {
  StyleSheet,
  Text,
  View,
  TextInput
} = React;


var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});


var Input = React.createClass({

    handleUpdateChange(text) {
        this.props.updateChange(text);
    },


  render() {

    return (
        <TextInput onChangeText={(text) => this.handleUpdateChange(text)} style={{ width : 200, height: 40, borderColor: 'gray', borderWidth: 1}} />
    );
  }
});




var ShowText = React.createClass({

    getInitialState(){
      return {
        text : '我是文字'
      }
    },

    handleChange(textValue){
      this.setState({
        text: textValue
      });
    },

    render() {

        return (
          <View style={{flex : 1,justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF'}}>
            <Text>{this.state.text}</Text>
            <Input updateChange={this.handleChange} />
          </View>
        );
    }
});

module.exports = React.createClass({

  render() {
    return (
      <View style={styles.container}>
        <ShowText />
      </View>
    );
  }
});

第二种情况:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');


var {
  StyleSheet,
  Text,
  View,
  TextInput
} = React;


var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});


var Input = React.createClass({

    handleUpdateChange(text) {
        this.props.updateChange(text);
    },


  render() {

    return (
      <View style={{flex : 1,justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF'}}>
        <TextInput onChangeText={(text) => this.handleUpdateChange(text)} style={{ width : 200, height: 40, borderColor: 'gray', borderWidth: 1}} />
      </View>
    );
  }
});




var ShowText = React.createClass({


    render() {

        return (
          <View style={{flex : 1,justifyContent: 'center',alignItems: 'center',backgroundColor: '#F5FCFF'}}>
            <Text>{this.props.text}</Text>
          </View>
        );
    }
});

module.exports = React.createClass({

  getInitialState(){
    return {
      text : '我是文字'
    }
  },

  handleChange(textValue){
    this.setState({
      text: textValue
    });
  },

  render() {
    return (
      <View style={styles.container}>
        <Input updateChange={this.handleChange} />
        <ShowText text={this.state.text} />
      </View>
    );
  }
});

reactjs 中 event 对象控制台输出null的问题

在 react 中输出 event 对象,在控制台查看是 null ,这对调试非常不友好。如图:

解决的办法很简单,在 console.log(event) 前使用event的 persist() 方法即可。

var Test = React.createClass({
        handleClick : function(e){
          e.persist()
          console.log(e);
            
        },

        render : function(){
           return (
                   <div onClick={this.handleClick}>我是DIV</div>
           )
        }

      });

      React.render(
              <Test />,
              document.getElementById('container')
      );