Canvas-入门

定义

canvas是HTML5新增的一个重要元素,先看下它的定义:

<canvas> is an HTML element which can be used to draw graphics using scripting (usually JavaScript).
This can, for instance, be used to draw graphs, make photo composition or simple (and not so simple) animations.

大意:

使用JS脚本可以绘制图形、简单的动画

注:canvas只是一个普通无色透明区域可理解为画布,同时绘制图形不是用鼠标,而是用JS脚本。

因为是新增元素,兼容性方面如下:

页面添加canvas

<canvas id="j-canvas" width="400" height="300">
    不支持canvas
</canvas>

注:

  1. 对不支持的浏览器显示,”不支持canvas”文案
  2. canvas不像img标签,canvas需要结束标签 < /canvas>
  3. canvas可以应用border、background-color、margin等属性
  4. canvas在没有指定宽高时,默认300 * 150

操作canvas

// 获得canvas
var canvas = document.getElementById('j-canvas');
// 获得画布
var ctx = canvas.getContext('2d');

检测支持性

var canvas = document.getElementById('j-canvas');

if (canvas.getContext) {
  var ctx = canvas.getContext('2d');
  // 支持处理
} else {
    // 不支持处理
}

坐标系

既然我们要在canvas上画东西,肯定要知道画的位置,所以就牵扯到坐标系的东西。
在canvas中坐标系分2D、3D坐标系统,除了默认的还有 坐标系转换。我们这次只探讨2D默认坐标系:

一个数据埋点的问题

背景:

上周负责收集收据的同事说前端埋点数据格式有问题,可是发送数据这块没有改动。怎么会出错?

发送数据代码:

let data = {
        act_k:'123',
        act_v:'456'
    }

$.ajax({
    url:'http://xxx.com',
    type:'get',
    data:data
})

需要传递给后端的格式如下:

http://xxx.com/?act_k=123&act_v=456

这次发送的数据格式:

let data = {
        act_k:'123123',
        act_v:{
            name:'name',
            age:20
        }
    }

发送如下:

http://xxx.com/?act_k=123123&act_v[name]=name&act_v[age]=20

看着数据是发送过去了,可是不符合后端接收要求:

http://xxx.com/?{"act_k":"123123","act_v":{"name":"name","age":20}}

对比两次发送的数据,看到act_k的值类型不同,第一个为字符串,第二个为对象。是不是这里的问题呢?可是前端业务代码都一样,所以只能会是 jQuery 的 ajax 方法在处理不同 data 时逻辑不同。

于是写了一个demo页面做测试,一步步跟踪发现如下代码:

// Convert data if not already a string
if ( s.data && s.processData && typeof s.data !== "string" ) {
    s.data = jQuery.param( s.data, s.traditional );
}

意思是若 data 是一个非字符串,则进行 jQuery.param 处理。

// <=1.3.2:
$.param({ a: [ 2, 3, 4 ] }); // "a=2&a=3&a=4"
// >=1.4:
$.param({ a: [ 2, 3, 4 ] }); // "a[]=2&a[]=3&a[]=4"

// <=1.3.2:
$.param({ a: { b: 1, c: 2 }, d: [ 3, 4, { e: 5 } ] });
// "a=[object+Object]&d=3&d=4&d=[object+Object]"

// >=1.4:
$.param({ a: { b: 1, c: 2 }, d: [ 3, 4, { e: 5 } ] });
// "a[b]=1&a[c]=2&d[]=3&d[]=4&d[2][e]=5"

传入我的数据:

act_k=123123&act_v[name]=name&act_v[age]=20

看来就是它了,jQuery.param 把我的数据给序列化了。如果不熟悉此方法,.serialize() 肯定在模拟表单提交时使用过。

jQuery.fn.extend( {
    serialize: function() {
        return jQuery.param( this.serializeArray() );
    },
    serializeArray: function() {
        return this.map( function() {

            // Can add propHook for "elements" to filter or add form elements
            var elements = jQuery.prop( this, "elements" );
            return elements ? jQuery.makeArray( elements ) : this;
        } )
        .filter( function() {
            var type = this.type;

            // Use .is( ":disabled" ) so that fieldset[disabled] works
            return this.name && !jQuery( this ).is( ":disabled" ) &&
                rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
                ( this.checked || !rcheckableType.test( type ) );
        } )
        .map( function( i, elem ) {
            var val = jQuery( this ).val();

            if ( val == null ) {
                return null;
            }

            if ( jQuery.isArray( val ) ) {
                return jQuery.map( val, function( val ) {
                    return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
                } );
            }

            return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
        } ).get();
    }
} );

serialize 也是调用了 jQuery.param,如何解决此问题?

看逻辑判断:

if ( s.data && s.processData && typeof s.data !== "string" ) {
    s.data = jQuery.param( s.data, s.traditional );
}

processData 为 false,或者 data 为字符串就行。

方法一:

$.ajax({
    url:'http://xxx.com',
    type:'get',
    data:data,
    processData:false // 新增
})

发送数据:

http://xxx.com/?[object Object]

真够彻底,一点也不处理。

方法二:

let data = {
        act_k:'123123',
        act_v:{
            name:'name',
            age:20
        }
    }

data = JSON.stringify(data);

发送数据:

http://xxx.com/?{"act_k":"123123","act_v":{"name":"name","age":20}}

ok,符合要求。

总结:

在业务代码一直没有改动的情况下,多数是因为输入参数变动,导致框架代码逻辑分之变动。

一个数字截取引发的精度问题(三)

上次总结的第四条: 当传入的参数小于数字的整数位时,返回指数形式表示的字符串。

let numObj = 12345.6
numObj.toPrecision(2) // '1.2e+4'

在JavaScript中有一个专门返回数字的指数形式的方法:toExponential()

numObj.toExponential([fractionDigits])

解释:

A string representing the given Number object in exponential notation with one digit before the decimal point, rounded to fractionDigits digits after the decimal point.

大意:

返回一个小数点前有一位数字且已按照小数点后指定的位数(fractionDigits)四舍五入后的指数形式的字符串。

let numObj = 77.1234;

console.log(numObj.toExponential());  // logs 7.71234e+1
console.log(numObj.toExponential(4)); // logs 7.7123e+1
console.log(numObj.toExponential(2)); // logs 7.71e+1
console.log(77.1234.toExponential()); // logs 7.71234e+1
console.log(77 .toExponential());     // logs 7.7e+1

注意:

  1. fractionDigits 取 0~20之间,其实就是小数点后有几个数字。
  2. 若numObj是一个没有小数点或者非指数形式的数字字面量,在调用时需要加一个空格,以防止解释器将”点”解释为小数点。
  3. 此方法也会进行四舍五入,作为金额计算时,要多加注意。

下篇将探究一下,经典问题:0.1 + 0.2 != 0.3。

一个数字截取引发的精度问题(二)

上篇文章只是简单介绍了 toFixed 方法,抽时间把 Number 里的一些方法又看了一下,其中有个方法引起我的注意:

Number.prototype.toPrecision()

precision 为”精度”的意思,貌似这个方法更符合上一篇文章所说的功能。可是事情并没有像我想象的一样,先看官方定义:

A string representing a Number object in fixed-point or exponential notation rounded to precision significant digits

大意:

返回一个定点和指数表示的同时四舍五入到指定位数的字符串。

再看一下使用方法,同时会总结出一些要点:

let PI = 3.1415926
console.log(PI.toPrecision(6)) // 3.14159
console.log(PI.toPrecision(4)) // 3.142
console.log(PI.toPrecision(2)) // 3.1

1.按指定的数字截取数字位数,同时四舍五入。

let numObj = 0.000123

console.log(numObj.toPrecision());    // '0.000123'
console.log(numObj.toPrecision(5));   // '0.00012300'
console.log(numObj.toPrecision(2));   // '0.00012'
console.log(numObj.toPrecision(1));   // '0.0001' 

2.没有传入参数时,返回数字的字符串形式,3.截取的位置从左边第一个非0的数字开始算起,不足补0。

let numObj = 12345.6
numObj.toPrecision(2) // '1.2e+4'

4.当传入的参数小于数字的整数位时,返回指数形式标识的字符串。

此方法用下来并没有达到我直觉认为的那样,这在一些金钱格式化上还不是很好用。

一个数字截取引发的精度问题(一)

上周有一个“收银台”的业务需要重构,其中有一个需求:

收益计算的结果,取小数点后两位但不进行四舍五入,若不足则补0。

看到这个需求你应该会第一个想到:

numberObj.toFixed([digits])

因为这个方法基本可以满足这个需求。但是当看到以前同事的方法时,感觉这个方法并不能完全满足:

/**
* 截断小数点后几位
* @val 数值
* @pos 小数点后截断的位置
*/
cutOffDecimal(val, pos) {
    // 把数字转换成字符串
    val = val.toString()
    let len = val.length
    let index = val.indexOf('.')
    let subVal = val; // 这是什么鬼?
    if (index != -1) {
      subVal = val.substring(0, index + pos +  1)
    }
    // 利用 toFixed 防止小数位达不到其位数要求
    return Number(subVal).toFixed(pos)
}

代码意思很明显,检测是否含有小数点,若有则用小数点的位置 + 精确的小数位置 + 1,因为substring最后一个位置不包括在内所以加1,
最后用toFixed补全。

他没有直接用toFixed,说明此方法不能直接满足。我查了一下API说明果然有猫腻:

The number is rounded if necessary

意思是此方法在必要时进行四舍五入,一看这个肯定不能直接满足此需求,我感觉上面代码写的也有点啰嗦,改写如下:

export function NumberPrecision(number,prec = 0){

    if(typeof number != 'number' || Number.isNaN(number)){
        console.error('Must be a number but not a NaN');
        return;
    }

    return number.toFixed(prec + 1).slice(0,-1);
}

1.类型判断,非数字以及NaN的则报错;

2.toFixed我没有直接取到目标位置,而是取到目标位置的下一个位置,这样就避免了该方法的四舍五入对结果造成的影响,然后再用slice截取字符串。

绚丽hover效果

周末在家,浏览网站,遇到这个

看到一个效果:

css-hover

然后就自己搞了一个,代码如下:

img{
    vertical-align: top;
}
.border-effect{
    display: inline-block;
    position: relative;
}
.figcaption{
    position: absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
}

.figcaption::before,
.figcaption::after{
    position: absolute;
    top:17px;
    right:17px;
    bottom: 17px;
    left:17px;
    content:'';
    opacity: 0;
    transition:all .5s ease, box-shadow .1s ease;
}
.figcaption::before{
    border-top: 1px solid #fff;
    border-bottom:1px solid #fff;
    transform: scale(0,1);
}
.figcaption::after{
    border-left:1px solid #fff;
    border-right:1px solid #fff;
    transform: scale(1,0);
}

.border-effect{
    cursor: pointer;
}

.border-effect:hover .figcaption::before,
.border-effect:hover .figcaption::after{
    opacity: 1;
    transform: scale(1); 
    box-shadow: 0 0 0 30px rgba(255,255,255,0.2);
    transition :all .5s ease, box-shadow .25s linear .20s;
}

JS Bin on jsbin.com

Nodjs重命名文件

“偷懒”是程序员第一天性,昨天把博客换了皮肤,今天早上起来就开始捣鼓文件命名问题。hexo的文章基本都是生成在_post文件夹下,

若文章多了以后就不好管理,所就打算以【日期+文章标题】为文件名:

更改前:

before

更改后:

after

文章一共90篇,作为程序员不可能一个个的去改,得用工具帮我去完成,那就是用程序帮我完成这么机械的任务。

完成这项任务的前提条件是:文章的时间和文章的标题

题目很简单,文件名就是,时间呢?幸好hexo在生成文章的时候,文件内容里面已经有时间:

title: Nodjs重命名文件
date: 2017-03-05 08:57:47
categories:
 - nodejs
 - javascript
tags:

所以接下来的事情就明确了:

1. 读取文章内容 
2. 提取时间 
3. 拼接时间和文章标题

代码整体思路:

1.读取指定目录下的文件内容
2.一行行的分割内容,提取想要的内容
3.拼接时间+标题

在整行读取内容遇到一些问题,查了文档nodejs好像有 https://nodejs.org/api/readline.html ,但我没有用直接用
了一个偷懒的方法分割内容:

data.toString().split('\n')

这样就得到一个以换行 \n 为分割的数组。

用到的方法:

readdirSync readFile rename

下面是代码:

/*
* @Author: zhuxy
* @Date:   2017-03-05 08:05:39
* @Last Modified by:   zhuxy
* @Last Modified time: 2017-03-05 09:23:35
*/
const fs = require('fs');
let path = 'your/path';
let files = fs.readdirSync(path);
for(let i = 0; i < files.length; i++){
    fs.readFile(`${path}/${files[i]}`,function(err,data){
        // console.log(data.toString().split('\n')[0].split(' ')[1])
        // console.log(data.toString().split('\n')[1].split(' ')[1])
        let newname = data.toString().split('\n')[1].split(' ')[1] + '-' + data.toString().split('\n')[0].split(' ')[1].replace(/['|']/g,'')
        fs.rename(`${path}/${files[i]}`,`${path}/${newname}.md`)
    })
}

新主题新想法

从上周五开始捣鼓了一个新主题,当然还有好多不完善,CSS代码很乱。主题参考了Vuejs的主题色,加上一些自己的想法。

刚开始就想把首页要弄点简约一些,也就是和别的页面区分开。

先放hexo官网和中文站:

https://hexo.io/

https://hexo.io/zh-cn/

在hexo里页面或者说模板分:

index post page archive category tag

同时hexo默认模板引擎是ejs,语法不是很习惯,mustache和handlebar的还是比较容易接受。

说了上面一些东西之后就要说说我是如何做的以及我为什么自己捣鼓主题。

1.如何做到首页与其他页面不同?

刚开始这个东西也浪费我一些时间,hexo用的是ejs模板引擎,貌似页面都是继承自layout.ejs,那我想能不能指定某些页面不继承呢?我搜了一些资料,

貌似都没有解决我的问题,如果有知道的请给我说下。既然这条路走不通,那也不是不能解决。

比如你想导航条在不同的页面显示不通

方法1:

不要再layout.ejs里面引入header.ejs,而是在上面说的(index/post/page等)模板里面单独引入或者单独写。

方法2:

使用hexo的内部方法,判断是否为具体页面然后展示不通的导航。

2.我为何要自己捣鼓?

为了搞清楚hexo是如何工作的,更重要的是在弄清楚之后,更好的定制自己想要的东西同时可以练习自己学到的知识,更快的应用一下。

当然现在这个主题还差很多东西,比如没有评论、分类、标签等等,以后慢慢弄得好看一些,代码写的好一点,然后贡献出去。