使用es6学canvas游戏开发

最新在看一本书《HTML5+JavaScript动画基础》, 基于这本书来学习Canvas游戏制作的。书很不错,介绍了很多游戏的基础概念。不过书里面的代码都是基于ES5编写的,现在是2018年了,所以打算边看边改写书里面的代码为ES6版本的。

前天晚上在做ch03/01-rotate-to-mouse.html这个例子,当时的代码大致如下:

    import {captureMouse} from '../include/utils.js'
    import Arrow from './classes/arrow.js'
    window.onload = () => {
      const canvas = document.getElementById('canvas')
      const context = canvas.getContext('2d')
      let mouse = captureMouse(canvas)
      let arrow = new Arrow()

      (function drawFrame () {
        window.requestAnimationFrame(drawFrame, canvas)
        context.clearRect(0, 0, canvas.width, canvas.height)
        const dx = mouse.x - arrow.x
        const dy = mouse.y - arrow.y
        arrow.rotation = Math.atan2(dy, dx)
        arrow.draw(context)
      }())
    };

诡异的问题

上面看着是没有任何问题的,可是执行的时候,一直报错:

Uncaught ReferenceError: arrow is not defined

当时就有点懵,没道理啊,drawFrame跟arrow的定义在同一个作用域,那drawFrame函数内部作用域里面肯定是能访问到外部作用域的arrow的,怎么可能没定义

当时有点怀疑自己是不是ES6没学好,这种IIFE的自执行函数难到在严格模式下有什么特殊的行为?

为了证实自己的猜测,改写了一下代码:

    import {captureMouse} from '../include/utils.js'
    import Arrow from './classes/arrow.js'
    window.onload = () => {
      const canvas = document.getElementById('canvas')
      const context = canvas.getContext('2d')
      let mouse = captureMouse(canvas)
      let arrow = new Arrow()

      function drawFrame () {
        window.requestAnimationFrame(drawFrame, canvas)
        context.clearRect(0, 0, canvas.width, canvas.height)
        const dx = mouse.x - arrow.x
        const dy = mouse.y - arrow.y
        arrow.rotation = Math.atan2(dy, dx)
        arrow.draw(context)
      }
      drawFrame()
    };

果然,代码顺利运行了。

然后我就以es6, scope, function, iife, variable, let这几个关键词苦苦Google,查了一堆网页,可就是没找到到底这个作用域是怎么影响的

为看缩小问题范围,于是重新写了一个demo:

    function foo(){
      var a = 1
      let b = 2
      (function bar() {
          console.log(a)
          console.log(b)
      }())
    }
    console.log(foo());

结果a能被正确log出来,b还是not defined

这下更证明自己的猜想了。可是其中的原理还是不懂

stackoverflow上的大牛

无奈之下,在stackoverflow上面发起了一个问题:a variable defined with let is not defined in a same scope IIFE

很快就有一个热心的大牛T.J. Crowder帮忙给编辑了一下问题,修复了一些语法上的错误,优化了代码展示。

然后这位大牛又顺便给解答了问题

答案跟我一直猜想的方向完全不一致,一切都是ASI(Automatic Semicolon Insertion)导致的。demo里面的代码,在实际被解析的时候,是大致长这样的:

    function foo(){
      var a = 1
      let b = 2(function bar() {
        console.log(a)
        console.log(b)
      }());
    }
    console.log(foo());

b的赋值和iife的执行连接到一起了,这也就是为什么函数体内b是not defined的原因,iife的执行先于b的定义

深入理解

关于ASI,是有了解的,不过这个知识点在我脑海里是跟代码压缩绑定在一起。初学js的时候,遇到过分号缺失导致的压缩代码执行错误,所以在js文件里面写代码的时候,会很注意这方面。

而这次是在html的script标签里面写代码,想着这些代码又不会被手动压缩,自然就没想过ASI的问题。

可实际上,任何js代码在解析执行的时候,都会在必要的时候经由解析器执行ASI来”补全分号”

总结

只要你在写js, 不管是在js文件里面还是script标签里面,分号都是一个值得严肃对待的事情

关于ASI的详细描述,可以参看以下两篇文章(我也是刚刚看的):

备胎的自我修养——趣谈 JavaScript 中的 ASI (Automatic Semicolon Insertion)

JavaScript ASI 机制详解

最近在尝试使用styled-components来做React下的样式开发,这样可以利用js语言的灵活来增强css的能力。

在多屏适配这块,一直是在使用淘宝的lib-flexible

在用styled-components处理px到rem的转换时,有点问题了。styled-components自己本着基础库的原则,是没有附加这些复杂的配置,所以一开始想的是自己写一个函数来做单位换算:

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定
    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

然后在写样式的时候这样写

// 保留了px的单位而不是传递数字,使得代码具有可读性,方便维护
let AvatarImage = styled(Image)`
    width: ${r('132px')}; 
    height: ${r('132px')};
`;

这样满足了基本需求。

不过作为喜欢偷懒喜欢折腾的程序员,总觉得这样写很啰嗦,所以想着能不能也跟styled-components那样使用标签模版的功能

所以也就有了第二版的function r:

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定

    // 针对template literals
    if (Array.isArray(pxValue)) {
        pxValue = pxValue[0];
    }

    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

在样式书写上,就可以这样写了:

let AvatarImage = styled(Image)`
    width: ${r`132px`};
    height: ${r`132px`};
`;

少写了两个括号,看着也清晰多了。

不过还是不太满意,因为样式里面如果数值一多,写起来还是有点费事。

既然能够实现单个单位的换算,那用正则表达式匹配所有样式字符串里面的px值,再替换为rem值,再把计算的结果返回给styled-components不就可以了。

想法是好的,不过在读取解析已有的样式模版字符串,正则匹配,还有如何把结果传递给styled-components这些方面,坑还是很多的,折腾了一个多小时,也总算解决了。

这里面,最核心的解析已有的样式模版字符串,styled-components提供了一个helper: css,使得工作量大大减少。

function r(pxValue) {
    const ratio = 20; // 根据项目配置比例的方式自行设定

    // 针对template literals
    if (Array.isArray(pxValue)) {
        pxValue = pxValue[0];
    }

    pxValue = parseInt(pxValue);

    return pxValue / ratio + 'rem';
}

// 把字符串样式里面的px单位换算成rem的
// 支持多行匹配
function transformPxToRem(style) {
    // 避免处理了函数等情况
    if (typeof style !== 'string') {
        return style;
    }

    return style.replace(/\d+px/gm, matched => {
        return r(matched);
    });
}

// 实现在把样式传递给styled之前,预先用transformPxToRem处理
function t(strings, ...interpolations) {
    let styles = css(strings, ...interpolations); // css是styled-components的一个helper
    styles = styles.map(transformPxToRem);

    // 模拟raw的调用
    return [[""], styles]
};

基于以上代码,在最终书写样式的时候就可以这样:

let AvatarImage = styled(Image)(...t`
    width: 132px;
    height: 132px;
`);

以函数的形式调用styled-compoent,而不是标签模版的形式,同时解构t函数的返回值,这样的结果和styled-components直接计算标签模版是一样的。

不过在实现这个功能之后,在写这篇文章的时候,发现lib-flexible不再用rem作为多端适配的方案了,而是改用的vm

不过最核心的预处理部分已经实现了,后面的调整还是很容易做的。

后期的计划:

现在css helper返回的是一个数组,可以考虑join成字符串,去掉多余的空白符,然后实现css to object和object to css。然后再加上plugin机制,使得整个系统更加灵活多变。

说着说着,好像实现了一个简版的postCss。

现在styled-components是有增加babel plugin的计划来打通postCss和styled-compoent,期待。

起因

今天尝试使用webpck的import()来做代码分割。

代码类似如下:

import('./nice-scroll').then(init => init(dom))

结果报错:

ERROR in ./js/utils/auto-set-height.js
Module build failed: SyntaxError: ‘import’ and ‘export’ may only appear at the top level (20:8)

分析

在package.json里面确认了一下,我用的webpack版本是^3.5.4,这个是一定支持import()方法的,那么问题应该就出在babel上了。

先截取我的babel-loader的部分配置:

use: {
    loader: 'babel-loader',
    options: {
        // 不采用.babelrc的配置
        "babelrc": false,
        "presets": [
            ["react"],
            ["es2015"]
        ],
        "plugins": [
            "transform-object-rest-spread",
            "transform-class-properties"
        ]
    }
}

一开始我的猜想是plugin es2015里面的transform-es2015-modules-commonjs先于webpack处理了代码,所以报错。

找了一下禁用的方法,改配置如下:

use: {
    loader: 'babel-loader',
    options: {
        // 不采用.babelrc的配置
        "babelrc": false,
        "presets": [
            ["react"],
            ["es2015", {module: false}]
        ],
        "plugins": [
            "transform-object-rest-spread",
            "transform-class-properties"
        ]
    }
}

还是不行。

后面一直各种关键词的搜索,偶然在github上面找到这个问题Dynamic `import()` crashing in `ModuleConcatenationPlugin`的一个回答:

Nope; babel will always process the code before webpack ever sees it; unless for some reason the file itself is being excluded from being processed by babel-loader.
This error is unrelated to babel; it’s a webpack issue.

这个回答,里面说到在webpack面对的代码都是babel处理过的,这个让我坚信问题还是在babel这块,继续搜索。

意外找到这个这篇文章:代码分离 – 使用import()。里面说到了一个插件:syntax-dynamic-import

Babel官方关于这个插件的描述是:

Syntax only
This plugin only allows Babel to parse this syntax. If you want to transform it then see dynamic-import-webpack or dynamic-import-node.

显然这是一个语法解析的插件,使得babel能够理解dynamic import的语法。再联系上面的报错信息里面的SyntaxError,结果有点明显了。

解决

npm install --save-dev babel-plugin-syntax-dynamic-import

然后调整babel-loader配置如下:

use: {
    loader: 'babel-loader',
    options: {
        // 不采用.babelrc的配置
        "babelrc": false,
        "presets": [
            ["react"],
            ["es2015", { "modules": false }]
        ],
        "plugins": [
            "syntax-dynamic-import",
            "transform-object-rest-spread",
            "transform-class-properties"
        ]
    }
}

重启webpack,顺利通过编译!!!

前情

最近在做活动编辑器的时候,考虑到运营人员很多都没有图片上传的权限,所以想在活动编辑器里面内置一个这样的功能,让运营可以使用上传控件或者托拽的方式上传图片到服务器,并在上传成功之后,自动把地址放到文本框里。

第一步要做的就是尝试着实现用FTP来上传一个文件到服务器的指定目录,同时基于文件的sha1值来命名上传之后的文件。

手上现有的两种实现路径是NodeJsPython,不过想到最近刚学Python,正需要这样一个机会,就先尝试python的方式。

开发

先是Google了一下,知道了一个ftplib的内置模块可以用来做FTP相关的功能。然后是之前在即刻上有看到余弦发的一个文章,说到用Python一句话查看文件的哈希值,上去找了找,也很快找到了那篇文章里面的相关代码。

折腾一下之后,实现代码如下:

import ftplib
import hashlib
import os

host = 'YOUR HOST'
username = 'YOUR NAME'
pwd = 'YOUR PWD'
port = 21
filePath = 'some/path/to/img.jpg'
serverPath = '/where/to/store/your/file/on/the/server/'

session = ftplib.FTP()

session.connect(host, port)

session.login(username, pwd)

session.cwd(serverPath)

file = open(filePath, 'rb')

filename, file_extension = os.path.splitext(filePath)

str_hash = hashlib.sha1(file.read()).hexdigest()

target_file_name = str_hash[:16]+file_extension

session.storbinary('STOR '+target_file_name, file)

file.close()

session.quit()

代码写完,就顺手执行看了下效果:文件顺利按的传到了预设的目录,也重命名为了指定的文件名。但是,文件的体积是0

调试

在Python的IDEL环境里面使用help命令查看了file相关的各种文档,最后把问题的方向指向了file.read。因为当时在IDEL环境里面,使用file.read读取了要上传的文件内容之后,再次调用file.read时,返回的内容为空。

于是在Google上面用python file read twice作为搜索词搜索了一下。果然找到了问题,文件里面有一个指针指向上一次读取的位置,默认是在最开始,也就是0。file.read会读取文件的全部内容,完成之后,指针就指向文件的最末尾。

所以,在调用过一次file.read之后,再次调用file.read,会从文件最末尾开始获取内容,这获取的内容自然就是空了。而ftp.storbinary在上传文件的时候,内部应该也是会使用类似file.read类似的方法来获取要上传的文件内容并传递给服务器。这个时候也自然就没法正常获取正确的文件内容

解决方式是在file.read之后,使用file.seek(0)来把文件的指针指回最开始。

最终代码:

import ftplib
import hashlib
import os

host = 'YOUR HOST'
username = 'YOUR NAME'
pwd = 'YOUR PWD'
port = 21
filePath = 'some/path/to/img.jpg'
serverPath = '/where/to/store/your/file/on/the/server/'

session = ftplib.FTP()

session.connect(host, port)

session.login(username, pwd)

session.cwd(serverPath)

file = open(filePath, 'rb')

filename, file_extension = os.path.splitext(filePath)

str_hash = hashlib.sha1(file.read()).hexdigest()

target_file_name = str_hash[:16]+file_extension

# 前面的file.read使得file的read cursor指向了文件的尾部
# 如果不手动调整指针的话,后面的文件上传,就会是一个空文件
file.seek(0)

session.storbinary('STOR '+target_file_name, file)

file.close()

session.quit()

参考文档

  1. Python Script Uploading files via FTP
  2. Extracting extension from filename in Python
  3. Why can’t I call read() twice on an open file?
  4. 我是如何 Python 一句话校验软件哈希值的

介绍

Array.sort([compareFunction])

对Array里面的元素进行升序排序

关于排序的规则

默认的排序

如果compareFunction没有提供的时候,默认把所有Array里面的元素转换为字符串,然后取第一个字符,比较它们的Unicode值,进行正序排序。

这个转为字符串再比较第一个字符的Unicode值,很关键,很多数字的比较,经常就是这样出错的。


[1,2,3].sort();  // ==> [1,2,3]  正确

[9, 80].sort();  // ==> [9, 80]  错误

[9, 80].sort();  // ==> [80, 9]  正确

上面例子中,数组[9, 80]进行sort的时候,先转为为字符串 “9”, “80”, 再比较第一个字符, “9”和”8″

根据这个比较结果进行排序,所以最后80会在9前面,而不是按照它们的数值大小进行排序的。

想自行测试的,可以看看这个demo(http://www.icondownloader.com/demo/sort-stable.html]

compareFunction的排序

进行sort的时候,如果compareFunction提供了,会往compareFunction传两个数组的元素(类似compareFunction(a, b))

取compareFunction的返回值,如果小于0,则表明a小于b;大于0,则a大于b

最后对结果进行升序排序,如果a小于b,则[a, b]; 如果a大于b 则[b, a]

这里有个技巧,在排序的时候,如果想实现降序排序,可以在compareFuction里面实现类似:return !(a-b);

sort stable

关于排序,有个stable的说法,也就是,排序完成之后,对于值相同的元素,在排序结束之后,能否保留其在排序之前的顺序。

sort stable

具体的概念可以参考维基百科https://en.wikipedia.org/wiki/Sorting_algorithm#Stability

目前的主流浏览器对stable的sort的支持情况如下:

Browser Sort is stable Sort is unstable
Firefox 8 all lengths
Safari 5.1.1 all lengths
Opera 11.52 all lengths
Internet Explorer 6, 7, 8 all lengths
Internet Explorer 9 all lengths
Android 2.3 Browser all lengths
Chrome 15 .. 17.0.942.0 length <= 10 length > 10

可以参考此网站,检测浏览器的sort情况http://ofb.net/~sethml/is-sort-stable.html

sort的效率

可以尝试着对compare function在执行的时候,进行一个计数。这样可以知道每次sort,总共执行了多少次compare function。

我这边写了个demo,有兴趣的可以在自己本机的各个浏览器上测试看看。http://www.icondownloader.com/demo/sort-compare-function-call-times.html

这边我贴出我自己Mac OSX Yosemite 10.10.4下的浏览器的运行结果

## chrome 版本 42.0.2311.135 (64-bit)
Array length is: 5 and sort call compare 7 times;
Array length is: 10 and sort call compare 23 times;
Array length is: 11 and sort call compare 22 times;
Array length is: 12 and sort call compare 19 times;
Array length is: 13 and sort call compare 27 times;
Array length is: 14 and sort call compare 25 times;
Array length is: 15 and sort call compare 24 times;
Array length is: 20 and sort call compare 29 times;
Array length is: 50 and sort call compare 80 times;
Array length is: 100 and sort call compare 206 times;
Array length is: 1000 and sort call compare 1996 times;
Array length is: 10000 and sort call compare 20206 times;

## Safari 版本 8.0.7 (10600.7.7)
Array length is: 5 and sort call compare 8 times;
Array length is: 10 and sort call compare 23 times;
Array length is: 11 and sort call compare 28 times;
Array length is: 12 and sort call compare 31 times;
Array length is: 13 and sort call compare 35 times;
Array length is: 14 and sort call compare 40 times;
Array length is: 15 and sort call compare 44 times;
Array length is: 20 and sort call compare 65 times;
Array length is: 50 and sort call compare 232 times;
Array length is: 100 and sort call compare 568 times;
Array length is: 1000 and sort call compare 8996 times;
Array length is: 10000 and sort call compare 123583 times;

## Firefox (Developer Edition) 40.0a2 (2015-05-29)
Array length is: 5 and sort call compare 6 times;
Array length is: 10 and sort call compare 26 times;
Array length is: 11 and sort call compare 24 times;
Array length is: 12 and sort call compare 33 times;
Array length is: 13 and sort call compare 48 times;
Array length is: 14 and sort call compare 48 times;
Array length is: 15 and sort call compare 37 times;
Array length is: 20 and sort call compare 70 times;
Array length is: 50 and sort call compare 245 times;
Array length is: 100 and sort call compare 574 times;
Array length is: 1000 and sort call compare 8600 times;
Array length is: 10000 and sort call compare 114046 times;

从上面的数据可以看出两个结论:

  1. chrome的执行效率明显高于Safari和Firefox。但是chrome的sort是unstable的,而Safari和Firefox是stable的。是不是可以认为,因为chrome不需要考虑stable,所以提高了执行效率。

  2. 可以看出随着数组长度的增加,比较的次数是指数增加的

用map来提高提高sort的性能

从上面的结论2可以知道,在数组长度很长的时候,compareFunction的调用次数是很多的,这个时候,提高compareFunction的效率就很有必要性了。

现在咱们构建一个数组

var arr = [],
    arrLen = 1000,
    i = 0;

function makeWord(){
    var word = [],
        words = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'],
        wordsLength = words.length,
        arrMinLen = 3,
        arrLen = arrMinLen + Math.floor( Math.random()*9 ),

        i = 0;

    for (i = 0; i < arrLen; i++){
        word.push( words[Math.floor( Math.random()*wordsLength )] );
    }

    return word.join('');
}

for (i = 0; i < arrLen; i++) {
    arr.push( makeWord() );
}

然后比较一下它们小写字母状态下的排序

// 未优化
arr.sort(function(a, b){
    return +(a.toLowerCase() > b.toLowerCase()) || +(a.toLowerCase() === b.toLowerCase())-1;
});

// 优化后

// 用一个临时数组来保存位置和计算后的数值
var mapped = list.map(function(el, i) {
  return { index: i, value: el.toLowerCase() };
})

// 排序这个已经计算后的临时数组
mapped.sort(function(a, b) {
  return +(a.value > b.value) || +(a.value === b.value) - 1;
});

// 根据位置信息 对应映射生成一个排序后的数组
var result = mapped.map(function(el){
  return list[el.index];
});
// 不支持map的时候的兼容方法

var tmpArr = [],
    result = [],
    i = 0,
    len = arr.length;

for (i = 0; i < len; i++) {
    tmpArr[i] = {index: i, value: arr[i].toLowerCase()};
}

tmpArr.sort(function(a, b) {
  return +(a.value > b.value) || +(a.value === b.value) - 1;
});

for (i = 0; i < len; i++) {
    result[i] = arr[tmpArr[i].index];
}

具体的执行效果可以看看jsperf里面的这个地址http://jsperf.com/arraysortperform

参考文章:
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Unicode
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#String_literals
+ http://ofb.net/~sethml/is-sort-stable.html
+ http://stackoverflow.com/questions/3026281/array-sort-sorting-stability-in-different-browsers

这份读书笔记是看了阮一峰的《React 入门实例教程》之后撰写的,留作记录。需要看原文的可以点击前面的书名访问对应链接。

这篇文章的目的

  • 介绍react
  • 如何下载react
  • 如何安装react
  • 如何使用react

文章的组织架构

从易到难的开始介绍。

react在页面的部署

  1. 依赖的两个js
  2. jsx 模板的标签规范
  3. 模板的编译应该放到服务器完成

模板语法

  1. React.render() 转换模板为HTML并插入节点
  2. 允许HTML和javascript混写。
    1. 规则是:遇到HTML标签(以<开头)则视为HTML,遇到代码块(以{)开头,视为javascript
    2. HTML内容可以直接写在javascript变量里
    3. javascript变量可以直接插入到模板。如果变量是数组,则自动展开这个数组的所有成员

组件

组件介绍

  1. 用React.createClass来生成组件类
  2. createClass的传参对象,需要一个render方法,用来描述组件的渲染
  3. 组件类以大驼峰方式书写
  4. 组件类以模板的方式调用,写法可以有单标签和双标签两种
  5. 组件类以模板的方式调用,实际是实例化了产生了一个对象,以下简称为组件
  6. 组件的调用和HTML标签完全一致,能加入属性。属性的调用,可以通过组件类的实例在javascript中,以this.props.xxx的方式调用
  7. 组件的style属性设定的时候,期望的值是一个对象{opacity: 0.5, marginLeft: ‘1px’} 而不是一个字符串,最终的写法是{{opacity: 0.5, marginLeft: ‘1px’}} 外面一对大括号表明是javascript代码块,里面的一对大括号,表明是对象
  8. 冲突的属性名class和for 要对应改为className和htmlFor,因为class和for是javascript的保留字
  9. this.props的属性和组件HTML属性一一对应,但是this.props.children例外,它表示组件的子节点
  10. 在用双标签的方式调用组件的时候,标签之间的HTML即为组件的子节点,完全和原生HTML一致

组件的特性

  1. findDOMNode
    1. virtual DOM
    2. virtual DOM 通过 DOM diff算法映射到真实DOM上
    3. findDOMNode用于获取跟组件关联的真实DOM的节点
    4. 只有在virtual DOM插入文档之后才能起作用
  2. this.state
    1. 类似状态机的概念
    2. getInitialState方法来设定初始状态
    3. 通过用户互动修改状态,一般是对应的event function来setState
    4. render里面,根据不同的状态渲染出不同的UI
    5. setState自动调用render重绘UI
    6. 区分this.props和this.state: this.props类似常量,设定之后不改变;this.state是随着用户互动而变化的特性
    7. 表单内容的变化也是通过state来进行更新的

组件生命周期

  1. 三个状态
    1. Mounting 已插入真实DOM
    2. Updating 正在被重新渲染
    3. Unmounting 已移出真实DOM
  2. 每个状态提供两个回调函数,will在进入状态之前调用,did在进入状态之后调用
  3. 三个状态共计五种回调函数,书写方式component是前缀,will/did是行为,Mount/Update/Unmount是状态
    1. componentWillMount()
    2. componentDidMount()
    3. componentWillUpdate(object nextProps, object nextState)
    4. componentDidUpdate(object prevProps, object prevState)
    5. componentWillUnmount()   为什么没有componentDidUnmount() ?
  4. 特殊状态的回调
    1. componentWillReceiveProps(object nextProps) 已加载组件收到新参数
    2. shouldComponentUpdate(object nextProps, object nextState) 组件判断是否重新渲染时调用 不明白

结合其他库或者框架使用

  1. react没有任何依赖
  2. react只关注表现层
  3. 如果希望对组件的DOM进行操作,尽量在componentDidMount中进行,保证真实DOM已经存在

主旨

React是一个关注表现层,以组件来构建内容,以状态机来完成交互的UI改变的,可以在浏览器端和服务器端同时运行的前端框架。

最近升级Wordpress的主题到twentyfifteen,清爽的界面看着果然高大上。然后问题来了,一直工作很好的分享工具JiaThis和uJian出问题了。找了客服问了下(自己太懒,不愿意debug),解决了JiaThis的问题,然后按照发现的问题的思路,顺便解决了uJian的问题。

JiaThis的分享按钮过小的问题

最开始的分享按钮是这样的

按钮很小

加上如下样式:

.jiathis_style img {
    max-width: 26px;
}

即可修复,变成

按钮正常

JiaThis的分享渠道面板

最开始的样式

无法展开

面板在鼠标放到按钮上的时候无法展开

加上如下样式:

.jiathis_style table{
    table-layout: auto;
}

使得表格的宽度计算方法恢复为默认的按内容来计算,样式就正常了

正常面板

uJian的展开样式

最开始是这样的扭曲

变形的uJian

加上和上面JiaThis的面板同样的样式之后

.jiathis_style table, #ujian-side table {
    table-layout: auto;
}

可以看的下去一点了

好一点的面板

再加上如下这段

#ujian-side a {
    box-sizing: content-box;
}

解决盒模型的计算方法

最终正常了:

效果? 看右侧的分享 ===============================>

所有的样式收集整理如下:

.jiathis_style img{ max-width:26px;}
.jiathis_style table,#ujian-side table{table-layout: auto;}
#ujian-side a{box-sizing: content-box;}

翻译自:http://learn.jquery.com/performance/optimize-selectors/

在越来越多的浏览器支持document.querySelectorAll()之后,使得查找DOM的压力从jQuery转移到浏览器,选择器的优化,已经没有以往那么重要。然而,仍然有一些小技巧要记住。

基于ID的选择器(ID-Based Selectors)

基于ID进行查找永远是最好的方法。

// 快速:
$( "#container div.robotarm" );
 
// 更加快速:
$( "#container" ).find( "div.robotarm" );

使用.find()的方法更快,因为第一个选择器是没法脱离Sizzle选择器引擎的处理 – 而只有一个ID不包含其他类型的选择器,是直接由document.getElementById来处理的,这个是原生方法,超级快

特异性(Specificity)

把更特异的选择器放右侧,更简单的选择器放左侧。(Sizzle选择器在某些情况下,是会从右往左进行查找,这个时候,右侧的特异性,使得查找出来的结果集很小,然后再往上遍历查找符合条件的父级,效率就会很高 — 编者译)

// 没优化的:
$( "div.data .gonzalez" );
 
// 优化了的:
$( ".data td.gonzalez" );

避免过度特异性(Avoid Excessive Specificity)

$( ".data table.attendees td.gonzalez" );
 
// 更好的: 如果可以尽量去除中间的多余的条件
$( ".data td.gonzalez" );

一个轻简的DOM有助于提升选择器的性能(原文是:A “flatter” DOM also helps improve selector performance,不太好翻译),因为这样可以在查找dom的时候,减少需要检索的层级

避免全部选择器(Avoid the Universal Selector)

如果明确的或隐晦的的全部选择器(结果如果是会匹配绝大多数的DOM),这样的选择器会非常的缓慢

$( ".buttons > *" ); // 非常耗性能
$( ".buttons" ).children(); // 更好的方式
 
$( ".category :radio" ); // 隐晦的全部选择器
$( ".category *:radio" ); // 和上面的效果一样,只是更加明确
$( ".category input:radio" ); // 更好的写法

今天要帮人实现一个可以在窗口上飘来飘去的广告,虽然对这类漂浮移动的广告很不感冒,但是写还是得写的。

原理知道,但是自己写,总是麻烦,就去网上找了个demo,然后再基于那个demo做了点修改。

话不多说,上代码:




    
    Document
    


效果看这里:

主要的修改有如下几点:

  • requestAnimationFrame — 使得在高级浏览器下的性能呢更好
  • document.documentElement.clientHeight/clientWidth — W3C标准的获取窗口尺寸的方法
  • 数据缓存 把部分固定不变的变量移动到动画主函数之外,避免多余的计算消耗和DOM查找

Bootstrap的Carousel默认是水平滚动的,下面介绍实现其垂直滚动的方式,看代码:

html结构,相对于bootstrap,新增了vertical的class



css样式,设定.carousel.vertical 对应的一些控制class的效果和动画

.vertical .carousel-inner {
  height: 100%;
}

.carousel.vertical .item {
  -webkit-transition: 0.6s ease-in-out top;
     -moz-transition: 0.6s ease-in-out top;
      -ms-transition: 0.6s ease-in-out top;
       -o-transition: 0.6s ease-in-out top;
          transition: 0.6s ease-in-out top;
}

.carousel.vertical .active {
  top: 0;
}

.carousel.vertical .next {
  top: 100%;
}

.carousel.vertical .prev {
  top: -100%;
}

.carousel.vertical .next.left,
.carousel.vertical .prev.right {
  top: 0;
}

.carousel.vertical .active.left {
  top: -100%;
}

.carousel.vertical .active.right {
  top: 100%;
}

.carousel.vertical .item {
    left: 0;
}​

js代码,设定滚动的间隔

$('.carousel').carousel({
  interval: 3000
})

以上就可以顺利实现一个垂直滚动的Carousel

参考网址:

twitter-bootstrap-carousel-vertical-sliding

Carousel vertical Bootstrap slider