0%


title: 滚动阅读 date: 2019-10-25 20:04:22

tags:

初步构思

  1. 定时任务启动
  2. 滑到面板桌面(从常规桌面上滑)
  3. 点击礼盒图标(右上角,测量位置)
  4. 点击完成相关任务(位置)
  5. 自动唤醒京东读书 app
  6. 点击图书(位置)
  7. 自动翻页(swipe)
  8. 完成后返回打卡页面
  9. 点击完成打卡(位置)

新增:

  1. 时间统计(共阅读了多久)
  2. 页数统计(共阅读了多少页)

防检测

自动翻页位置

  1. 滑动翻页(swipe) Math.random
  2. 点击翻页(touch),时间和位置都随机
  3. 映射音量键翻页

该机分辨率为 1440*720

下一页触摸区域在 x(480-720),y(0-1440)

规则:

30 分钟阅读 300 页,则每分钟阅读 10 页,至少 6 秒阅读完一页.

1
6000 * (Math.random() + 1);

每页最少 6 秒,最多 12 秒不到.

每 300 页最少 30 分钟.最多 60 分钟不到.

随机时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var initTime = 6000 * (Math.random() + 0.33333);
if (initTime < 6000) {
initTime += 4000;
}
//或者

function iTime() {
var initTime = 6000 * (Math.random() + 0.33333);
if (initTime < 6000) {
return (initTime += 4000);
}
return initTime;
}

iTime();

每页最少 2 秒,最多 8 秒不到.

每 300 页最少不到 16 分钟.最多 40 分钟不到.

定时运行脚本

点击脚本右边的菜单按钮->更多->定时任务设置定时运行脚本.注意必须保持 Auto.js 后台运行(自启动白名单、电源管理白名单等),不可加锁屏密码.

脚本的开头使用 device.wakeUp()来唤醒屏幕

1
2
3
4
5
//# 启动app
app.launchApp("京东读书");

//暂停运行5秒
sleep(5000);

气泡信息toast(message)

注意,信息的显示是”异步”执行的,并且,不会等待信息消失程序才继续执行。如果在循环中执行该命令,可能出现脚本停止运行后仍然有不断的气泡信息出现的情况

官方貌似说了会实时显示进度,那么下面的统计就不需要了.

这里可以显示已经看了几页(估计得写个函数),看具体是怎么翻页了

1
2
3
4
5
如果触发一次, 就次数`total += 1`;
if (total == 300) {
//toast('看完300页了,准备打卡去')
//建议此时不停,不知道时间够不够,继续刷一会
}
1
2
3
4
5
6
7
if (total >= 300 && totalTime >= 30 * 6000) {
//条件满足,停止刷页
stop();
//跳转打卡页面
} else {
//继续刷页
}

立即停止exit()

console.show()

显示控制台。这会显示一个控制台的悬浮窗(需要悬浮窗权限)。

text {string} | {Object}要打印到控制台的信息,相当于 log(text)。

基于坐标的触摸模拟

要获取要点击的位置的坐标,可以在开发者选项中开启”指针位置”。

setScreenMetrics(width, height)

设置脚本坐标点击所适合的屏幕宽高.

如果脚本运行时,屏幕宽度不一致会自动放缩坐标.

安卓 7.0 以上的触摸和手势模拟

click(x, y)

模拟点击坐标(x, y),并返回是否点击成功。只有在点击执行完成后脚本才继续执行。

使用该函数模拟连续点击时可能有点击速度过慢的问题,这时可以用press()函数代替。

press(x, y, duration)

duration: {number} 按住时长,单位毫秒

模拟按住坐标(x, y), 并返回是否成功。只有按住操作执行完成时脚本才会继续执行。

如果按住时间过短,那么会被系统认为是点击;如果时长超过 500 毫秒,则认为是长按。

swipe(x1, y1, x2, y2, duration)

x1 {number} 滑动的起始坐标的 x 值

y1 {number} 滑动的起始坐标的 y 值

x2 {number} 滑动的结束坐标的 x 值

y2 {number} 滑动的结束坐标的 y 值

duration {number} 滑动时长,单位毫秒

模拟从坐标(x1, y1)滑动到坐标(x2, y2),并返回是否成功。只有滑动操作执行完成时脚本才会继续执行。

随机数 random

random(min, max)

min {number} 随机数产生的区间下界

max {number} 随机数产生的区间上界

返回 {number}

返回一个在[min...max]之间的随机数(正整数)。例如random(0, 2)可能产生 0, 1, 2。

random()

返回 {number}

返回在[0, 1)的随机浮点数。

device.wakeUp()

唤醒设备。包括唤醒设备 CPU、屏幕等。可以用来点亮屏幕。

device.wakeUpIfNeeded()

如果屏幕没有点亮,则唤醒设备。

按键模拟

实体按键模拟依赖 root 权限,故放弃.

基于控件的操作

推荐使用auto()函数来确保无障碍服务已启用.

auto([mode])

检查无障碍服务是否已经启用,如果没有启用则抛出异常并跳转到无障碍服务启用界面;同时设置无障碍模式为 mode。

如果不加 mode 参数,则为正常模式。

建议使用auto.waitFor()auto.setMode()代替该函数,因为auto()函数如果无障碍服务未启动会停止脚本;而auto.waitFor()则会在在无障碍服务启动后继续运行。

auto.waitFor()

检查无障碍服务是否已经启用,如果没有启用则跳转到无障碍服务启用界面,并等待无障碍服务启动;当无障碍服务启动后脚本会继续运行。

点击文本click(text[, i])

text {string} 要点击的文本

i {number} 如果相同的文本在屏幕中出现多次,则 i 表示要点击第几个文本, i 从 0 开始计算

该函数可以点击大部分包含文字的按钮。例如微信主界面下方的”微信”, “联系人”, “发现”, “我”的按钮。

通常与 while 同时使用以便点击按钮直至成功。例如:

1
while (!click("扫一扫"));

UiSelector.exists()

返回 {Boolean}

判断屏幕上是否存在控件符合选择器所确定的条件。

例如要判断某个文本出现就执行某个动作,可以用:

1
2
3
if (text("某个文本").exists()) {
//要支持的动作
}

暂停 sleep()

1
2
//暂停1秒
sleep(1000);

选取控件

一般软件的界面是由一个个控件构成的,例如图片部分是一个图片控件(ImageView),文字部分是一个文字控件(TextView);同时,通过各种布局来决定各个控件的位置,例如,线性布局(LinearLayout)里面的控件都是按水平或垂直一次叠放的,列表布局(AbsListView)则是以列表的形式显示控件。

控件有各种属性,包括文本(text), 描述(desc), 类名(className), id 等等。我们通常用一个控件的属性来找到这个控件,例如,想要点击 QQ 聊天窗口的”发送”按钮,我们就可以通过他的文本属性为”发送”来找到这个控件并点击他,具体代码为:

1
2
var sendButton = text("发送").findOne();
sendButton.click();

在这个例子中, text(“发送”)表示一个条件(文本属性为”发送”),findOne()表示基于这个条件找到一个符合条件的控件,从而我们可以得到发送按钮 sendButton,再执行 sendButton.click()即可点击”发送”按钮。

果一个控件是图片控件.我们注意到这个图标的 desc(描述)属性为”搜索”,那么我们就可以通过 desc 属性来定位这个控件,得到点击搜索图标的代码为:

1
desc("搜索").findOne().click();

另外,对于这个搜索图标而言,id 属性也是唯一的,我们也可以用 id(“action_search”).findOne().click()来点击这个控件。如果一个控件有 id 属性,那么这个属性很可能是唯一的.

i_reader_folder

定时器 Timers

setTimeout()

与 web 端用法一致.但是使用了一个不同的内部实现,它是基于 Android Looper-Handler 消息循环机制构建的。其实现机制与 Node.js 比较相似.

setInterval(callback, delay[,…args])

callback {Function} 当定时器到点时要调用的函数。

delay {number} 调用 callback 之前要等待的毫秒数。

…args {any} 当调用 callback 时要传入的可选参数。

因为定时器是异步执行,另外 delay 只能是 number,那么不采用这种方案.

刷页函数

1
2
3
function turnPage(){
...
}

准备工作

先打开京东读书,登录账号

点击书城-免费-点击一本书-加入书架-(多加几本)

方案一: 点击翻页

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
//检查无障碍服务开启情况
auto.waitFor();
//显示控制台
console.show();
//设置脚本适合的宽高
setScreenMetrics(720, 1440);
//打开京东读书app
app.launchApp("京东读书");
toast("开始打卡");

//等待启动
sleep(2000);

//跳过
if (text("跳过").exists()) {
//点击控件
text("跳过").findOne().click();
sleep(2000);
}

//检测升级
if (text("下次再说").exists()) {
//点击控件
text("下次再说").findOne().click();
sleep(2000);
}

//点击控件,跳转书架
id("main_tab_bookshelf").findOne().click();
sleep(1000);

//准备开始图书

//随机时间
var initTime = 6000 * (Math.random() + 0.33333);
if (initTime < 6000) {
initTime += 4000;
}

这里有问题

应该阅读到末尾结束,出现关闭阅读.点击关闭阅读.删除第一本.重新打开一本,开始阅读.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//已读完时,左上角出现关闭阅读
if (text("关闭阅读").exists()) {
//点击控件
text("关闭阅读").findOne().click();
sleep(2000);
//长按第一本书,左下角删除,弹出确认删除,点击第二个删除.sleep(2000),右上角完成,继续阅读()
let index;
deleteBook();
//继续阅读
//删掉之后,加上已阅读200页,重新开始,又会阅读300页.
this.index = index;

readBook();
} else {
//执行自动阅读
readBook();
}

开始阅读-阅读完-跳出阅读-删除-重新开始阅读

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
//执行阅读主步骤
mianStep()

function mianStep(){
readeBook()
if(text("关闭阅读").exists()){
//点击控件
text("关闭阅读").findOne().click()
sleep(2000)
//长按第一本书,左下角删除,弹出确认删除,点击第二个删除.sleep(2000),右上角完成,继续阅读()
let index
deleteBook()
//继续阅读
//删掉之后,加上已阅读200页,重新开始,又会阅读300页.
this.index = i
mianStep()
}

//自动阅读
function readBook(){
toast("开始阅读")
//选择第一本
id("i_reader_folder").indexInParent(0).findOne().click()
sleep(3000)
//执行点击
clickPage()
}

//点击阅读
function clickPages(){
//点击300页
for(let {i=0}=index; i<300; i++){
randomClick()
//间隔时间
sleep(initTime)
}
}

//定时器版
// for(var i=0; i<5; i++){
// setTimeout(randomClick(),initTime)
// }

//随机点坐标
function randomClick(){
//随机坐标,各缩减20px,防止误触
x = random(500, 700)
y = random(20, 1420)
//点击随机坐标区域翻页
click(x, y)
}


//删除已读数目
function deleteBook(){
//长按图书,坐标就是第一本书的位置
//press(x, y, 1500)
id("i_reader_folder").indexInParent(0).findOne().longClick()
//删除图书
text("删除").findOne().click()
//要确认删除了
id("confirm").findOne().click()
//右上角完成
text("完成").findOne().click()
}



//点击打卡文本
click(text["立即打卡"])

//打印结束
toast("打卡结束")

方案二: 滑动翻页


title: vue可复用性&组合

date: 2019-10-14 15:08:18

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • Vue.js # 一级分类
  • Vue.js进阶 # 二级分类

混入 mixin

一个混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被”混合”进组件本身的选项.

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义一个混入对象
var myMixin = {
created: function(){
this.hello()
},
methods: {
hello: function(){
console.log('hello from mixin")
}
}
}

//定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})

var component = new Component() //=> 'hello from mixin'

选项合并

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行”合并”.

比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var mixin = {
data: function () {
return {
message: "hello",
foo: "abc",
};
},
};
new Vue({
mixins: [mixin],
data: function () {
return {
message: "goodbye",
bar: "def",
};
},
created: function () {
console.log(this.$data);
// => { message: 'goodbye', foo:'abc', bar: 'def'}
},
});

同名钩子函数将合并成一个数组,因此都将被调用.另外,混入对象的钩子将在组件自身钩子之前调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mixin = {
created: function () {
console.log("混入对象钩子被调用");
},
};

new Vue({
mixins: [mixin],
created: function () {
console.log("组件钩子被调用");
},
});

//=> '混入对象钩子被调用'
//=> '组件钩子被调用'

值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

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
var mixin = {
methods: {
foo: function () {
console.log("foo");
},
conficting: function () {
console.log("from mixin");
},
},
};

var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log("bar");
},
conficting: function () {
console.log("from self");
},
},
});

vm.foo(); // => "foo"
vm.bar(); // => "bar"
vm.conflicting(); // => "from self"

注意:Vue.extend() 也使用同样的策略进行合并。

全局混入

混入也可以进行全局注册,使用时需要小心.一但使用全局混入,他将影响每一个之后创建的 Vue 实例.

使用恰当时还可以用来为自定义选项注入处理逻辑.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption;
if (myOption) {
console.log(myOption);
}
},
});

new Vue({
myOption: "hello!",
});
// => "hello!"

请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。

自定义选项合并策略

自定义选项将使用默认策略,即简单地覆盖已有值。如果想让自定义选项以自定义逻辑合并,可以向 Vue.config.optionMergeStrategies 添加一个函数:

1
2
3
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
};

对于多数值为对象的选项,可以使用与 methods 相同的合并策略:

1
2
var strategies = Vue.config.optionMergeStrategies;
strategies.myOption = strategies.methods;

可以在 Vuex 1.x 的混入策略里找到一个更高级的例子:

1
2
3
4
5
6
7
8
9
10
const merge = Vue.config.optionMergeStrategies.computed;
Vue.config.optionMergeStrategies.vuex = function (toVal, fromVal) {
if (!toVal) return fromVal;
if (!fromVal) return toVal;
return {
getters: merge(toVal.getters, fromVal.getters),
state: merge(toVal.state, fromVal.state),
actions: merge(toVal.actions, fromVal.actions),
};
};

自定义指令

输入框聚焦

当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:

1
2
3
4
5
6
7
8
//注册一个全局自定义指令v-focus
Vue.directive("focus", {
//当被绑定的元素插入到DOM中时...
inserted: function (el) {
//聚焦元素
el.focus();
},
});

局部指令注册,组件中接受directive:

1
2
3
4
5
6
7
8
directives: {
focus: {
//指令的定义
inserted: function(el){
el.focus()
}
}
}

然后在模板中任何元素上使用v-focus属性,例如:

1
<input v-focus>

钩子函数

一个指令对象可以提供以下几个钩子函数:

  1. bind: 只调用一次,指令第一次绑定到元素时调用.在这里进行一次性的初始化设置.
  2. inserted: 被绑定元素插入到父节点时调用.
  3. update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前.指令的值可能发生改变,也可能没有.
  4. componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用.
  5. unbind: 只调用一次,指令与元素解绑时调用.

钩子函数的参数

指令钩子会被传入以下参数:

  • el: 指令所绑定的元素,可以直接操作 DOM.

  • binding: 一个对象,包含以下属性:

    • name: 指令名,不包括v-前缀.
    • value: 指令的绑定值.例如:v-my-directive="1 + 1"中,绑定值为 2。
    • oldValue: 指令绑定的上一个值.仅在 update 和 componentUpdated 钩子中可用.无论值是否改变都可用.
    • expression: 字符串形式的指令表达式.例如 v-my-directive="1 + 1"中,表达式为 "1 + 1"
    • arg: 传给指令的参数.可选.例如 v-my-directive:foo 中,参数为 “foo”。
    • modifiers: 一个包含修饰符的对象.例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode: Vue 编译生成的虚拟节点.

  • oldVNode: 上一个虚拟节点.

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//html
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>

//js
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})

new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})

动态指令参数

v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!

例如创建一个自定义指令,通过固定布局将元素固定在页面上.

1
2
3
4
<div id="baseexample">
<p>Scroll down this page</p>
<p v-pin="200">Stick me 200px from the top of the page<p>
</div>

自定义事件

1
2
3
4
5
6
7
8
9
10
Vue.directive('pin', {
bind: function(el, bingding, vnode){
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})

new Vue({
el: '#baseexample"
})

这会把元素固定在距离页面顶部 200px 的位置.

如果需要将元素固定在左侧呢?

1
2
3
4
<div id="dynamicexample">
<p>Scroll down this page</p>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.<p>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.directive("pin", {
bind: function (el, binding, vnode) {
el.style.position = "fixed";
var s = binding.arg == "left" ? "left" : "top";
el.style[s] = binding.value + "px";
},
});

new Vue({
el: "#dynamicexample",
data: function () {
return {
direction: "left",
};
},
});

函数简写

在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:

1
2
3
Vue.directive("color-swatch", function (el, binding) {
el.style.backgroundColor = binding.value;
});

对象字面量

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

1
2
3
4
5
6
7
//html
<div v-demo="{ color: 'white', text: 'hello!' }"></div>;
//js
Vue.directive("demo", function (el, binding) {
console.log(binding.value.color); // => "white"
console.log(binding.value.text); // => "hello!"
});

渲染函数&JSX

渲染函数在 Vue.js 进阶里有讲到,JSX 先不看了.

插件

插件用来为 Vue 添加全局功能.

插件常用功能:

  1. 添加全局方法或属性.
  2. 添加全局资源: 指令,过滤器,过渡
  3. 通过全局混入来添加一些组件选项.
  4. 添加 Vue 实例方法,通过把他们添加到 Vue.prototype 上实现.
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能.如 Vue-router

使用插件

通过全局方法Vue.use()使用插件.需要在new Vue()之前调用.

也可以传入一个可选的对象.

1
Vue.use(MyPlugin, { someOption: true });

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

Vue.js 官方提供的一些插件 (例如 vue-router) 在检测到 Vue 是可访问的全局变量时会自动调用 Vue.use()。然而在像 CommonJS 这样的模块环境中,你应该始终显式地调用 Vue.use()

开发插件

Vue.js 的插件暴露一个 install 的方法.这个方法的第一个参数是 Vue 构造器,第二个参数是可选的选项对象:

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
MyPlugin.install = function (Vue, options) {
//1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
//逻辑..
};

//2.添加全局资源
Vue.directive("my-directive", {
bind(el, binding, vnode, oldVnode) {
//逻辑
},
//...
});

//3. 注入组件选项
Vue.mixin({
created: function () {
//逻辑...
},
//...
});

//4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
//逻辑...
};
};

过滤器

过滤器可以用在:双花括号插值和 v-bind 表达式

1
2
3
4
5
<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

在组件的选项中定义本地的过滤器

1
2
3
4
5
6
7
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}

或者在创建 Vue 实例之前全局定义过滤器:

1
2
3
4
5
6
7
8
9
Vue.filter("capitalize", function (value) {
if (!value) return "";
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
});

new Vue({
// ...
});

当全局过滤器和局部过滤器重名时,会采用局部过滤器。

过滤器可以串联

1
2
3
4
5
{
{
message | filterA | filterB;
}
}

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

过滤器可以接收参数:

1
2
3
4
5
{
{
message | filterA("arg1", arg2);
}
}

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 ‘arg1’ 作为第二个参数,表达式 arg2 的值作为第三个参数。


title: vue过渡和动画

date: 2019-10-14 12:08:18

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • Vue.js # 一级分类
  • Vue.js进阶 # 二级分类


title: 移动端适配
date: 2019-10-09 21:30:02
tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • css # 一级分类
  • 移动端 # 二级分类

媒体查询 media-query

当媒体查询为 true 时,相关的样式表或样式规则会按照正常的级联规被应用。 当媒体查询返回 false, 标签上带有媒体查询的样式表仍将被下载 (只不过不会被应用)。

包含了一个媒体类型和至少一个使用宽度、高度和颜色等媒体属性来限制样式表范围的表达式。CSS3 加入的媒体查询使得无需修改内容便可以使样式应用于某些特定的设备范围。

1
2
3
4
5
6
7
8
<style>
@media (min-width: 700px) and (orientation: landscape){
// 大于700px执行display:none
.sidebar {
display: none;
}
}
</style>

视口 iewport

为什么要设置 viewport
viewport 的设置不会对 PC 页面产生影响,但对于移动页面却很重要。下面我们举例来说明:

  1. 媒体查询 @media 响应式布局中,会根据媒体查询功能来适配多端布局,必须对 viewport 进行设置,否则根据查询到的尺寸无法正确匹配视觉宽度而导致布局混乱。如不设置 viewport 参数,多说移动端媒体查询的结果将是 980px 这个节点布局的参数,而非我们通常设置的 768px 范围内的这个布局参数
  2. 由于目前多数手机的 dpr 都不再是 1,为了产出高保真页面,我们一般会给出 750px 的设计稿,那么就需要通过设置 viewport 的参数来进行整体换算,而不是在每次设置尺寸时进行长度的换算。

viewport 详解

在移动端有三种类型的 viewport: layoutviewport、visualviewport、idealviewport。具体解释如下:

layoutviewport:   大于实际屏幕, 元素的宽度继承于 layoutviewport,用于保证网站的外观特性与桌面浏览器一样。layoutviewport 到底多宽,每个浏览器不同。iPhone 的 safari 为 980px,通过 document.documentElement.clientWidth 获取。
visualviewport: 当前显示在屏幕上的页面,即浏览器可视区域的宽度。
idealviewport: 为浏览器定义的可完美适配移动端的理想 viewport,固定不变,可以认为是设备视口宽度。比如 iphone 7 为 375px, iphone 7p 为 414px。

viewport 设置

<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
通过对 meta 标签三个 viewport 的设置,最终使页面完美展示。

  1. width 设置的是 layoutviewport 的宽度
  2. initial-scale 设置页面的初始缩放值,并且这个初始缩放值是相对于 idealviewport 缩放的,最终得到的结果不仅会决定 visualviewport,还会影响到 layoutviewport
  3. user-scalable 是否允许用户进行缩放的设置

只要 layoutviewport === visualviewport,页面下面不会出现滚动条,默认只是把页面放大或缩小。

设备像素比 dpr 与 1px 物理像素

物理像素(physical pixel)

手机屏幕上显示的最小单元,该最小单元具有颜色及亮度的属性可供设置,iphone6、7、8 为:750 _ 1334,iphone6+、7+、8+ 为 1242 _ 2208

设备独立像素(density-indenpendent pixel)

此为逻辑像素,计算机设备中的一个点,css 中设置的像素指的就是该像素。老早在没有 retina 屏之前,设备独立像素与物理像素是相等的。

设备像素比(device pixel ratio)

设备像素比(dpr) = 物理像素/设备独立像素。如 iphone 6、7、8 的 dpr 为 2,那么一个设备独立像素便为 4 个物理像素,因此在 css 上设置的 1px 在其屏幕上占据的是 2 个物理像素,0.5px 对应的才是其所能展示的最小单位。这就是 1px 在 retina 屏上变粗的原因,目前有很多办法来解决这一问题。

1px 的物理像素的解决方案

从第一部分的讨论可知 viewport 的 initial-scale 具有缩放页面的效果。对于 dpr=2 的屏幕,1px 压缩一半便可与 1px 的设备像素比匹配,这就可以通过将缩放比 initial-scale 设置为 0.5=1/2 而实现。以此类推 dpr=3 的屏幕可以将 initial-scale 设置为 0.33=1/3 来实现。

设备像素比 dpr 与 rem 的适配方案

rem 是相对于根元素 html 的 font-size 来做计算。通常在页面初始化时加载时通过对 document.documentElement.style.fontSize 设置来实现。

rem 适配规则

通过对 initial-scale = 1/dpr 的设置,已将对屏幕的描述从物理像素转化到了物理像素上了,这将是后续推导的基础,且设计稿为 750px。

物理像素为 750 = 375 _ 2,若屏幕等分为 10 份,那么 1rem = 75px,10rem = 750px;
物理像素为 1125 = 375 _ 3,若屏幕等分为 10 份,那么 1rem = 112.5px, 10rem = 1125px;
物理像素为 1242 = 414 * 3, 若屏幕等分为 10 份,那么 1rem = 124.2px, 10rem = 1242px;

同时为了书写方便可以直接通过 px 布局,然后在打包时利用 pxtorem 库转化为基于 rem 的布局。

视口单位(vw,vh)适配方案

将视口宽度 window.innerWidth 和视口高度 window.innerHeight 等分为 100 份,且将这里的视口理解成 idealviewport 更为贴切,并不会随着 viewport 的不同设置而改变。

vw : 1vw 为视口宽度的 1%
vh : 1vh 为视口高度的 1%
vmin :  vw 和 vh 中的较小值
vmax : 选取 vw 和 vh 中的较大值

如果设计稿为 750px,那么 1vw = 7.5px,100vw = 750px。其实设计稿按照设么都没多大关系,最终转化过来的都是相对单位,上面讲的 rem 也是对它的模拟。这里的比例关系也推荐不要自己换算,使用 pxtoviewport 的库就可以帮我们转换。当然每种方案都会有其弊端,这里就不展开讨论。


title: canvas画板

date: 2019-08-27 13:39:26

tags: html5

categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • HTML5 # 一级分类
  • canvas # 二级分类

简介

canvas 本身没有绘图能力,只是定义了一个容器,都是由 canvas 内部的 CanvasRenderingContext2D 对象来做,需要我们用 JavaScript 脚本完成绘制工作。

基础

  1. html 引入<canvas>标签

<canvas id="canvas"></canvas>

  1. js 获取<canvas>属性,使用 2d 绘制上下文.
1
2
3
var canvas = getElementById("canvas");
var context = canvas.getContext("2d");
//使用context进行绘制
  1. 设置宽高

使用canvas.width设置宽度.

canvas.height设置高度.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<canvas id="canvas" sytle="border: 1px solid #aaa;display: block;margin: 50px auto;"></canvas>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas")
canvas.width: 1024;
canvas.height: 768;
if(canvas.getContext("2d")){
//如果浏览器支持canvas,可以使用
var context = canvas.getContext("2d")
}else{
alert('当前浏览器不支持,请使用谷歌浏览器')
}
}
</script>
</body>

设置全屏

1
2
3
4
5
let pageWidth = document.documentElement.clientWidth;
let pageHeight = document.documentElement.clientHeight;

canvas.width = pageWidth;
canvas.height = pageHeight;

绘制线段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var context = canvas.getContext("2d");
conext.beginPath(); //开始第一段绘制,与其他绘制可以区别开
context.moveTo(100, 100); //以画布左上角为原点,从坐标(100,100)开始
context.lineTO(700, 700); //把线画到(700,700)位置
context.lineTO(100, 700); //折到(100,700)位置
context.lineTO(100, 100); //线段完成封闭,形成三角形
context.closePath(); //结束本段状态

context.fillStyle = "rgab(168,168,168)"; //设置三角形填充颜色
context.fill(); //执行填充颜色

context.lineWidth = 5; //设置线段宽度
context.strokeStyle = "red"; //设置线段颜色
context.stroke; //执行绘图

conext.beginPath(); //开始第二段绘制,与其他绘制可以区别开
context.moveTo(200, 100); //以画布左上角为原点,从坐标(200,100)开始
context.lineTO(700, 600); //把线画到(700,600)位置
context.closePath(); //结束本段状态

context.strokeStyle = "blue"; //设置线段颜色
context.stroke; //执行第二段绘图
  1. 标签属性

    标签 描述
    height 设置 canvas 的高度
    width 设置 canvas 的宽度
    fill() 可以填充
    fillRect(x,y,width,height) 绘制一个矩形边框
    fillRect(x,y,width,height) 绘制一个填充的矩形
    clearRect(x,y,width,height) 清除指定矩形区域,让清除部分完全透明
    ctx.strokeStyle 可以改变画笔颜色
    ctx.beginPath() 设置开始路径
    ctx.moveTo(x,y) 设置起点
    ctx.lineTo(x,y) 设置终点
    ctx.stroke() 绘制
    ctx.closePath() 结束路径
    ctx.arc(弧形圆心 x 坐标,y 坐标,半径,起始角(以 3 点钟的位置开始),结束角、方向(true 表示逆时针,false 表示顺时针)) 绘制一个弧形
    ctx.quadraticCurveTo(cpx,cpy,x,y)参数是控制点 x 坐标,控制点 y 坐标,结束点 x 坐标,结束点 y 坐标 绘制二次贝塞尔曲线
    ctx.quadraticCurveTo(cpx1,cpy1,cpx2,cpy2,x,y)参数是控制点 1 的 x 坐标,控制点 1 的 y 坐标,控制点 2 的 x 坐标,控制点 2 的 y 坐标,结束点 x 坐标,结束点 y 坐标 绘制三次贝塞尔曲线
  2. canvas 画板代码实现

html

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="actions"class="actions x">

<svg id="pen"class="icon">
<use xlink:href="#icon-pencil"></use>
</svg>

<svg id="eraser"class="icon">
<use xlink:href="#icon-eraser"></use>
</svg>

<svg id="clear"class="icon">
<use xlink:href="#icon-delete"></use>
</svg>

<svg id="download"class="icon">
<use xlink:href="#icon-download"></use>
</svg>


<ul class='colorCir'>
<li id="black"class="black"></li>
<li id="red"class="red"></li>
<li id="yellow"class="yellow"></li>
<li id="blue"class="blue"></li>
</ul>

<ul class='lineWidth'>
<li id="thin" class="thin"></li>
<li id="thick" class="thick"></li>
</ul>
</div>
</body>
</html>

css

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
*{
margin:0;
padding:0;
}
body{
background:#eee
}
ul,ol{
list-style: none;
}

.colorCir{
top:60px;
left:20px;
position: fixed;
}
.colorCir li{
border: 1px solid grey;
height:20px;
width: 20px;
border-radius: 50%;
margin-bottom:10px;
}

.colorCir .red{
background:red;
}
.colorCir .yellow{
background:yellow;
}
.colorCir .blue{
background:blue;
}
.colorCir .black{
background:black;
}

.colorCir .red.active{
transform: scale(1.2);
transition: all 0.3s;
}
.colorCir .yellow.active{
transform: scale(1.2);
transition: all 0.3s;
}
.colorCir .blue.active{
transform: scale(1.2);
transition: all 0.3s;
}
.colorCir .black.active{
transform: scale(1.2);
transition: all 0.3s;
}

.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}

body{
overflow: hidden;
}
#canvas{
display: block;
position: fixed;
top:0px;
left:0px;
}

.actions{
position: fixed;
top:0px;
left:0px;
padding-top:20px;
padding-left:15px;
}
.actions svg{
width:1.5em;
height:1.5em;
transition: all 0.3s;
margin:0 5px;
}

.actions svg.active{
fill:red;
transform: scale(1.2);
}

.lineWidth{
position: relative;
left:-10px;
top:140px;
}

.lineWidth .thin{
height:0px;
width:40px;
border-top:2px solid black;
margin:15px;
}

.lineWidth .thick{
height:0px;
width:40px;
border-top:6px solid black;
margin:15px;
}

.lineWidth .thin.active{
transform: scale(1.2);
}

.lineWidth .thick.active{
transform: scale(1.2);
}

js

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

var using = false;
var lastPoint = {
x: undefined,
y: undefined,
};

/*画板逻辑 */
autoSetSize(canvas);

listenToUser(canvas);

/********/
//画笔、橡皮擦按钮替换
var eraserEnabled = false;

pen.onclick = function () {
eraserEnabled = false;
pen.classList.add("active");
eraser.classList.remove("active");
};
eraser.onclick = function () {
eraserEnabled = true;
eraser.classList.add("active");
pen.classList.remove("active");
};

//颜色替换并高亮
red.onclick = function () {
context.fillStyle = "red";
context.strokeStyle = "red";
red.classList.add("active");
yellow.classList.remove("active");
blue.classList.remove("active");
black.classList.remove("active");
};
yellow.onclick = function () {
context.fillStyle = "yellow";
context.strokeStyle = "yellow";
yellow.classList.add("active");
red.classList.remove("active");
blue.classList.remove("active");
black.classList.remove("active");
};
blue.onclick = function () {
context.fillStyle = "blue";
context.strokeStyle = "blue";
blue.classList.add("active");
yellow.classList.remove("active");
red.classList.remove("active");
black.classList.remove("active");
};
black.onclick = function () {
context.fillStyle = "black";
context.strokeStyle = "black";
black.classList.add("active");
yellow.classList.remove("active");
blue.classList.remove("active");
red.classList.remove("active");
};
/********/

thin.onclick = function () {
thin.classList.add("active");
thick.classList.remove("active");
context.lineWidth = 2;
};

thick.onclick = function () {
thick.classList.add("active");
thin.classList.remove("active");
context.lineWidth = 4;
};

clear.onclick = function () {
context.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
};

download.onclick = function () {
var url = canvas.toDataURL("image/png");
var a = document.createElement("a");
document.body.appendChild(a);
a.href = url;
a.download = "my drawing";
a.click();
};

//drawLine
function drawLine(x1, y1, x2, y2) {
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
context.closePath();
}

//drawCir
function drawCir(x, y) {
context.beginPath();
context.arc(x, y, 0.1, 0, Math.PI * 2);
context.fill();
}

//重置canvas画板宽高
function setCanvasSize() {
var pageWidth = document.documentElement.clientWidth;
var pageHeight = document.documentElement.clientHeight;

canvas.width = pageWidth;
canvas.height = pageHeight;
}

//自动设置canvas画板宽高
function autoSetSize() {
setCanvasSize();
window.onresize = function () {
setCanvasSize();
};
}
function preventBehavior(e) {
e.preventDefault();
}

document.addEventListener("touchmove", preventBehavior, false);

function listenToUser() {
//特性检测
if (document.body.ontouchstart !== undefined) {
//是触屏设备
canvas.ontouchstart = function (aaa) {
var x = aaa.touches[0].clientX;
var y = aaa.touches[0].clientY;
using = true;
lastPoint = { x: x, y: y };
if (eraserEnabled) {
context.clearRect(x - 10, y - 10, 20, 20);
} else {
drawCir(x, y);
}
};
//
canvas.ontouchmove = function (aaa) {
var x = aaa.touches[0].clientX;
var y = aaa.touches[0].clientY;
var newPoint = { x: x, y: y };
if (using) {
if (eraserEnabled) {
context.clearRect(x - 10, y - 10, 20, 20);
} else {
drawCir(x, y);
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
lastPoint = newPoint;
}
}
};
canvas.ontouchend = function (aaa) {
using = false;
};
} else {
//不是触屏设备
canvas.onmousedown = function (aaa) {
var x = aaa.clientX;
var y = aaa.clientY;
using = true;
lastPoint = { x: x, y: y };
if (eraserEnabled) {
context.clearRect(x - 10, y - 10, 20, 20);
} else {
drawCir(x, y);
}
};

//鼠标移动监听
canvas.onmousemove = function (aaa) {
var x = aaa.clientX;
var y = aaa.clientY;
var newPoint = { x: x, y: y };
if (using) {
if (eraserEnabled) {
context.clearRect(x - 10, y - 10, 20, 20);
} else {
drawCir(x, y);
drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
lastPoint = newPoint;
}
}
};
//鼠标松开监听
canvas.onmouseup = function (aaa) {
using = false;
};
}
}


title: Chart.js

date: 2019-10-03 11:30:37

tags: html5

categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • HTML5 # 一级分类
  • canvas # 二级分类

安装

而另一个版本 Chart.bundle.js 和 Chart.bundle.min.js 早就包含了 Moment.js 。

1
npm install chart.js --save

CDN 引入

1
<script src="https://cdn.bootcss.com/Chart.js/2.8.0-rc.1/Chart.bundle.min.js"></script>

模块化引入

1
2
import Chart from 'chart.js';
var myChart = new Chart(ctx, {...});

简单上手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<canvas id="myChart" width="400" height="400"></canvas>
</body>

<script>
// 获取我们要选择的canvas元素的上下文

var ctx = document.getElementById("myChart").getContext("2d");
var myNewChart = new Chart(ctx, {...});


// 使用jQuery获取上下文-使用jQuery的.get()方法。
var ctx = $("#myChart").get(0).getContext("2d");

// 这将获取jQuery集合中第一个返回的节点。
var myNewChart = new Chart(ctx);
</script>

如果您希望所有创建的图表都具有响应性,并在浏览器窗口打开时调整大小,则可以更改以下设置:

1
Chart.defaults.global.responsive = true;

折线图

折线图是在一条线上绘制数据点的方法.

1
2
3
4
5
var myLineChart = new Chart(ctx, {
type: "line",
data: data,
options: options,
});

数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var data = {
//横坐标显示的值
labels: [2014, 2015, 2016, 2017, 2018, 2019],
datasets: [
{
//第一条线表示优酷
label: "优酷",
//设定data数值,表示每年用户总量
data: [100, 200, 400, 800, 1600],
},
{
//第二条线表示YouTube
label: "YouTube",
data: [50, 100, 200, 400, 600],
},
],
};

设置坐标轴

Y 轴

options.yAxes

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
var options = {
//设置缩放
scales: {
//设置Y轴
yAxes: [
{
//刻度
ticks: {
//固定最大最小
min: 0,
max: 2000,

//建议最大最小
suggestedMin: 0,
suggestedMax: 2000,

//跨度
stepSize: 400,

//用回调函数设置单位
callback: function (value, index, values) {
return value + "人";
},
},
},
],
},
};

标题和图例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var options = {
//设置标题
title: {
display: true,
text: '阿银',
fontColor: 'black',
fontSize: '24',
position: 'top'
},
legend: {
//设置图例
position: 'left',
display: true
},
animation: {
//动态效果,为0时禁用动画
duration: 1000,
easing: 'easeOutQuart'
}

线型变换

data.datasets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var data = {
labels: [2014,2015,2016,2017,2018,2019],
datasets: [
{
label: '优酷',
data: [100,200,400,800,1600],
//线段颜色
borderColor: 'red',
//线宽
borderWidth: 3,
//背景色是否填充
fill: true,
//背景色
backgroundColor: 'rgba(128,0,0,0.5),
//线段曲率,为0显示折线
lineTension: 0,
//折点的造型,默认是圆点.可选三角,方块,星星
pointStyle: 'rect'
},{
label: 'YouTube',
data: [50,100,200,400,600]
}
]
}

条形图(柱状图)

垂直条形图: type: bar

水平条形图: type: horizontalBar

大部分属性类似于折线图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var data = {
labels: [2014, 2015, 2016, 2017, 2018],
datasets: [
{
label: "优酷",
data: [100, 200, 400, 800, 1600],
//为每个条形单独设置颜色
backgroundColor: ["red", "orange", "yellow", "green", "blue"],
},
{
label: "YouTube",
data: [50, 100, 200, 400, 600],
},
],
};

还可以设置条形重叠(覆盖)

1
2
3
4
5
6
7
8
9
10
11
12
options: {
scales: {
xAxes: [{
//设置覆盖
stacked: true
}],
yAxes: [{
//设置覆盖
stacked: true
}]
}
}

混合图

线形图和条形图的混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var mixedChart = new Chart(ctx, {
type: "bar",
data: {
datasets: [
{
label: "优酷",
data: [100, 200, 400, 800, 1600],
},
{
label: "YouTube",
data: [50, 100, 200, 400, 600],
//再改成线图
type: "line",
//防止填充遮盖到条形图
fill: false,
},
],
labels: [2014, 2015, 2016, 2017, 2018],
},
options: options,
});

雷达图(六边形图)

type = 'radar'

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
type = "radar";
var data = {
labels: ["力量", "速度", "经验", "防守", "技术", "发球"],
datasets: [
{
label: "帝国の破坏龙-马龙",
data: [5, 5, 5, 5, 5, 5],
backgroundColor: "rgba(247,247,9,0.2)",
},
{
label: "帝国の绝凶虎-张继科",
data: [4, 5, 5, 5, 5, 5],
backgroundColor: "rgba(238,61,17,0.2)",
},
],
};

var options = {
scale: {
ticks: {
suggestedMin: 0,
suggestedMax: 5,
stepSize: 1,
},
},
title: {
display: true,
text: "桌球帝国战力值",
fontSize: "24",
},
};

饼图和甜甜圈

饼图: type: 'pie'

甜甜圈: type: 'doughnut'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data = {
datasets: [
{
data: [30, 20, 30],
backgroundColor: ["green", "red", "yellow"],
},
],

labels: ["马龙", "张继科", "王浩"],
};

var options = {
//50-用于甜甜圈,0-用于饼图
cutoutPercentage: 80,
title: {
display: true,
text: "桌球帝国战力值",
fontSize: "24",
},
};

极区图

type: 'polarArea'

极区图类似于饼图,但是每个线段具有相同的角度-线段的半径根据值而不同。


title: TypeScript 入门 date: 2019-10-03 16:00:08

tags:

简介

TypeScript 具有类型系统,且是 JavaScript 的超集。 它可以编译成普通的 JavaScript 代码。 TypeScript 支持任意浏览器,任意环境,任意系统并且是开源的。

安装

1
2
3
4
5
6
7
8
9
npm install -g typescript
//查看版本号
tsc -v
//查看安装目录
npm bin -g

//代码编译
tsc helloworld.ts
//会出现一个helloworld.js

编译

输出文件夹

1
2
//将文件编译到dist文件夹下
tsc --outDir dist helloworld.ts

tsconfig.json

1
2
3
4
5
6
7
8
//使用代码初始化,生成tsconfig.json
tsc --init

//修改文件内属性
//设置输出文件夹dist
"outDir": "./dist"
//设置源代码文件夹
"rootDir": "./src"

动态监视

发现代码变动,自动编译

1
tsc - w;

变量使用

  1. 变量使用前要定义
  2. 变量类型
  3. any 类型

变量使用前要定义

let/var 变量名:变量类型 = 默认值

变量类型

number,string,boolean,symbol,any,object

1
2
3
let name: string = "Tom";
console.log("My name is" + name);
name = 100; //编译错误,类型不匹配

any 类型

1
2
3
4
5
//any类型让编译器忽略检测
let name: any = "Tom";
console.log("My name is" + name);
name = 100;
console.log("My name is" + name);

常量保存

常量定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//常量的定义语法
const name: type = initial_value;

//数据库连接串
const dbstr: string = "ip=192.168.2.1;port=5433;uid=ayin;pwd=123123;";
console.log(dbstr);

//数组常量
const DATA: number[] = [100, 200, 300];
console.log(DATA);

// DATA = [10,20,30]; //编译错误

//数组地址并未改变,值可以改变
//正常赋值
DATA[0] = 10;
DATA[1] = 20;
DATA[2] = 30;
console.log(DATA); //[10,20,30]

//数组元素增加
DATA[3] = 40;

数组的使用

定义方法

let name:type[] = initial

1
2
3
4
5
6
7
8
9
10
//定义数组
let data: string[] = ["金", "木", "水", "火"];
console.log(data); //['金','木','水','火']
console.log(data[0]); //金

//追加赋值
data[4] = "土";

//越界赋值
data[10] = "风";

多维数组的定义

let name:type[][] = [[],[],[],...]

1
2
3
4
5
6
7
8
9
10
11
12
//定义三行五列的数组
let data: number[][] = [
[1, 2, 3, 4, 5],
[10, 20, 30, 40, 50],
[100, 200, 300, 400, 500],
];

//打印第一行数组
console.log(data[0]); // [1,2,3,4,5]

//打印第一行第一个元素
console.log(data[0][0]); // 1

枚举类型

定义方法

enum name = {name1, name2, name3, ...}

注意定义的是常量,后期不会变化.

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
//使用枚举
enum Sex {
MALE,
FEMALE,
UNKNOWN
}

let member_sex: Sex = Sex.FEMALE //默认为FEMALE
console.log(member_sex) //输出1
console.log(Sex[member_sex]) //输出FEMALE

//判断中枚举需要在传入对象前加字符+
switch (+member_sex){
case Sex.MALE:
console.log("男")
break
case Sex.FEMALE:
console.log("女")
break
case Sex.UNKNOWN:
console.log("未知")
break
}

//或者将判断套入函数中
function checkSex(sex: Sex){
let result:string = ''
switch (sex){
case Sex.MALE:
console.log("男")
break
case Sex.FEMALE:
console.log("女")
break
case Sex.UNKNOWN:
console.log("未知")
break
}
return result
}

联合类型

指包含多种类型的变量类型,在实际项目中不常用.

1
2
3
4
5
6
7
8
9
10
11
//联合类型的定义
let data: type1 | type2 | type3 | ...

//联合类型的使用
let mydata: string | boolean

mydata = "hello TS"
console.log(mydata) //hello TS

mydata = true
console.log(mydata) //true,没有报错

null 空检查

空检查用于代码编译时对程序中的变量做 null 空值检查,避免业务级别的错误.

1
2
3
4
5
6
7
8
9
let data1: string = undefined
let data2: string = null
let data3: string = ""
data3 = null

//在tsconfig.json中开启
"strickNullChecks": false

//输入tsc进行检查

第三方库的使用

node-request 库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import request = require('request')

request('http://api.komavideo.com/news/list', function(error, response, body){
if(error){
console.error(error)
}else{
console.log(body)
var body = JSON.parse(body)
for(var i = 0; i < body.length; i++){
var item = body[i]
console.log(item.title)
}
}
})
//直接使用是会报错的

需要添加 tsd 文件

如何找 tsd 文件

网站: http://microsoft.github.io/TypeSearch

直接搜索

导入 tsd 文件

1
npm install --save @types/request

函数的使用

1
2
3
4
//格式
function fname(param1:type, param2:type,...) : return_type {
//函数内容
}

范例

1
2
3
4
5
6
7
8
function add(x: number, y: number): number {
return x + y;
}
console.log(add(10, 5));

//利用变量
let func_add = add;
console.log(func_add(1, 2));

箭头函数

范例

1
2
3
4
add(x:number, y:number) : number => {
return x + y
}
console.log(add(10, 5))

可省略参数

定义符号: ?

1
2
3
4
5
6
7
8
9
10
11
12
13
sayHello(name?: string): string => {
if(name == undefined) {
return "Hello ayin"
}else{
return "Hello " + name
}
}
console.log(sayHello("Tom")) //Hello Tom
console.log(sayHello()) //Hello ayin

//默认值形式
sayHello(name: string = "ayin"): string => {
return "Hello " + name

扩展运算符

定义符号: ...

1
2
3
4
5
6
7
8
add(...vals: number[]): number => {
let result = 0
for(let val of vals){
result += val
}
return result
}
console.log(add(1,2,3)) //6

类的定义

类似于 ES6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
name: sting;
sex: number; //0:女,1:男

constructor(name: string, sex: number) {
this.name = name;
this.sex = sex;
}

sayHello() {
console.log(`${this.name},你好${this.sex}`);
}
}

let qq = new Person("花藤", 1);
qq.sayHello();

console.log(qq.name);
console.log(qq.sex);

类的访问限制

类的访问修饰符

  • public: 共有访问
  • protected: 本类和子类访问
  • private: 本类访问
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
class Datebase{
dbname: string

constructor(bdname: string){
this.dbname = name
}
}

//外部调用
public showDB(){
console.log(`数据库:${this.dbname}`)
}

//本类和子类调用
protected connect(){
console.log(`${this.name}`,"连接中...")
}

//本类调用
private disconnect(){
console.log(`${this.name}`,"关闭")
}

let oracle = new Database("甲骨文")
oracle.showDB()
//oracle.connect()
//oracle.disconnect()

class PostgreSql extends Database{
public doIt(){
super.connect()
//super.disconnect()
}
}

let postgres = new PostregSql("小象")
postgres.showDB()
postgres.doIt()

getter 和 setter

书接上回,由于private属于私有,无法直接访问.可以使用 get 进行访问.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Datebase{
private dbname: string

constructor(bdname: string){
this.dbname = name
}

get name(): string {
return this.dbname
}

set name(val: string){
this.dbname = val
}
}

//通过name属性访问到
let db = new Database('oracle is good')
console.log(db.name)

//修改name属性
db.name = "mysql is better'
console.log(db.name)


title: Git版本管理工具date: 2019-10-01 13:44:59

tags:

下载安装

去官网下载吧

建立Git库

1
2
3
4
5
//先新建一个空文件夹
mkdir newBash
//在git bash中
cd newBash //切换到newBash文件夹下
git init //初始化仓库

设置基础信息

配置相关信息

1
2
3
4
5
6
7
//在git bash中
git init
git config -l //查看当前配置
git config --global user.name "你的用户名"
git config --global user.email "邮箱地址"
git config --global color.ui true //代码颜色高亮显示
git config -l //再查看一下对不对

提交commit

基本步骤:

  1. 建立文件(本地工作文件夹)
  2. 追加文件(索引区stage)
  3. 提交文件(本地库local,远程库remote)
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
//新建一个文件
touch a.md

//在文件中写字符
echo "helloworld" > a.md

//查看此时状态
git status (未提交文件名颜色为红色)

//提交到索引区(也叫暂存区)
git add a.md (或者直接git add . 表示提交修改文件)

//再看看状态
git status (提交了颜色变绿了)

//将暂存区的文件提交到本地库(需要附带说明)
git commit -m "add file" //add file 就是说明,会显示在github的文件名后面

//再看看状态
git status (提交到本地库后,暂存区里就空了)

//看看提交历史
git log
//(会显示提交者的名字,时间,提交记录)

//提交到远程仓库
git push origin master

远程仓库

一般远程仓库有一个master主分支,多个其他分支.

现在展示如何从远端dev分支下载并修改,再推送.

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
//切换到一个文件夹下
cd newclone
//从远端仓库clone
git clone git@xxx.com

//查看当前分支
git branch (发现远端仓库只有master被clone下来,其他没有)

//查看所有分支(包括远端分支)
git branch -a (此时出现远端dev分支)

//创建dev分支,映射远端dev分支
git branch dev remotes/origin/dev

//上面几步不执行也可以,直接执行切换分支就可以clone数据

//切换到dev分支
git checkout dev (此时dev下所有文件都会clone下来)

//修改其中的文件
...

//推送素质三连
git add .
git commit -m "修改情况"
git push origin dev

分支branch

新建分支

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

//查看当前分支状态
git branch

//新建一个分支 dev
git branch dev

//切换到 dev 分支
git checkout dev

//修改文件
nano style.css
...

//提交 dev 分支中内容到索引区
git add .

//提交 dev 分支到本地库
git commit -m "added style.css"

//查看记录
git log

//切换到主分支 master
git checkout master

合并分支

书接上回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//查看分支状态
git branch

//在主分支 master 下,合并 dev 分支
git merge dev

//查看记录
git log

//查看后没有问题,删除开发分支
git branch -d dev

//最后查看分支状态
git branch

分支冲突

案例分析:

在dev分支创建index文件并修改.在主分支创建index文件并修改.合并分支,必有冲突

代码重现:

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

//创建 dev 分支并切换到 dev
git checkout -b dev

//创建文件.修改文件
nano index.html
...

//提交操作
git add .
git commit -m "modified by dev"

//切换到主分支
git checkout master

//重复上述操作
nano index.html
...

git add .
git commit -m "modified by master"

//查看分支状态
git branch

//合并分支(必有冲突)
git merge dev

//报错,在冲突文件中出现提示

//由 leader 决定以哪个分支的信息为准
//编辑文件,去掉冲突提示,选择以哪个分支的修改为准
nano index.html
...

//重新提交,并将
git add .
git commit -m "meraged by leader."

查看提交历史

书接上回

1
2
3
4
5
6
7
8
9
10
11
12
13

//提交之后,查看提交历史
git log

//查看最近几次历史,接一个数字
git log -3 (查看最近 3 次的)

//把历史记录缩成一行.简略显示
git log --oneline

//详细显示
git log -p (非常详细)

提交回退

1
2
3
4
5
6
7

//把刚修改的文件退到未修改状态
git checkout -- a.md (取消修改,恢复到初始状态)

//查看状态
git status (文件已恢复)

提交到索引区,想回退

1
2
3
4
5
6
7

//把文件从索引区头部退出
git reset HEAD a.md

//把刚修改的文件退到未修改状态
git checkout -- a.md

版本回退

回退到远程库最后提交的版本,覆盖本地工作区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//回退到最后一次
git reset --hard HEAD

//回退到倒数第二次
git reset --hard HEAD~

//回退到倒数第三次
git reset --hard HEAD~2

//回退到指定版本
git reset --hard 5d79467
(后面的数字是提交时的 ID)

//如果没记住 ID,通过 reflog 找到现在位置的 id,再从过去返回来.
git reflog (会出现所有最近的记录,可以加-3,显示最近 3 条记录)

比较修改内容

修改文件后

1
2
3
4
5
6
7
8
9
10

//和修改前进行比较
git diff

//把修改后的文件加到索引区
git add .

//和索引区的进行比较
git diff --cached

文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14

//将文件添加到索引区
git add [file1 file2 ...]

//将工作文件夹下所有文件添加到索引区
git add .

//删除
git rm
//删除索引区文件
git rm --cached a.md
//更名
git mv a.md b.md

Git忽略管理

设置Git忽略文件,这些文件不参与Git库提交.

1
2
3
4

//新建.gitignore 文件
在该文件中写入要忽略的文件名

更新最后的提交

最后一条提交代码里有bug,想更改,又不想新增一条提交.把更改追加到最后一条提交里.

1
2
3
4
5
6
7
8
9
10

git commit -m "最后一次提交"

//发现 bug,修改 bug 后
//此时文件已修改
git add .

//提交到最后一次修改里
git commit --amend

使用Tag标签

系统版本号管理

例如: 1.1.3

格式: NNN.abc.xxx

  • NNN: 大版本号
  • abc: 每次做出小的更新时发布的版本号
  • xxx: 每次bug修正时发布的版本号
1
2
3
4
5
6
7
8
9
10

//查看当前版本号
git tag

//设置当前版本号
git tag v1.0.0

//查询该版本号时文件状态
git show v1.0.0



title: 前端安全防范

date: 2019-09-20 15:56:10

tags:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • HTML # 一级分类
  • HTTP安全 # 二级分类

XSS 攻击

什么是 XSS 攻击?如何防范 XSS 攻击?什么是 CSP?

XSS 简单点来说,就是攻击者想尽一切办法将可以执行的代码注入到网页中。

XSS 可以分为多种类型,但是总体上我认为分为两类:持久型和非持久型。

持久型也就是攻击的代码被服务端写入进数据库中,这种攻击危害性很大,因为如果网站访问量很大的话,就会导 致大量正常访问页面的用户都受到攻击。

举个例子,对于评论功能来说,就得防范持久型 XSS 攻击,因为我可以在评论中输入以下内容<script>alert(2)</script>.

这种情况如果前后端没有做好防御的话,这段评论就会被存储到数据库中,这样每个打开该页面的用户都会被攻击到。

非持久型相比于前者危害就小的多了,一般通过修改 URL 参数的方式加入攻击代码,诱导用户访问链接从而进行攻击。

举个例子,如果页面需要从 URL 中获取某些参数作为内容的话,不经过过滤就会导致攻击代码被执行.

1
2
<!-- http://www.domain.com?name=<script>alert(1)</script> -->
<div></div>

但是对于这种攻击方式来说,如果用户使用 Chrome 这类浏览器的话,浏览器就能自动帮助用户防御攻击。但是我 们不能因此就不防御此类攻击了,因为我不能确保用户都使用了该类浏览器.

防御

对于 XSS 攻击来说,通常有两种方式可以用来防御。

转义字符

首先,对于用户的输入应该是永远不信任的。最普遍的做法就是转义输入输出的内容,对于引号、尖括号、斜杠进 行转义

1
2
3
4
5
6
7
8
9
10
function escape(str) {
str = str.replace(/&/g, '&')
str = str.replace(/</g, '<')
str = str.replace(/>/g, '>')
str = str.replace(/"/g, '&quto;')
str = str.replace(/'/g, ''')
str = str.replace(/`/g, '`')
str = str.replace(/\//g, '/')
return str
}
1
2
3
//通过转义可以将攻击代码  <script>alert(1)</script>  变成
// -> <script>alert(1)</script>
escape("<script>alert(1)</script>");

但是对于显示富文本来说,显然不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。对于这种情况,通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。

CSP(白名单)

CSP 本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何 拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。

通常可以通过两种方式来开启 CSP:

  1. 设置 HTTP Header 中的  Content-Security-Policy
  2. 设置 meta 标签的方式 <meta http-equiv="Content-Security-Policy">

这里以设置 HTTP Header 来举例

1
2
3
4
5
6
//只允许加载本站资源
Content-Security-Policy: default-src ‘self’
//只允许加载 HTTPS 协议图片
Content-Security-Policy: img-src https://*
//允许加载任何来源框架
Content-Security-Policy: child-src 'none'

CSRF 攻击

什么是 CSRF 攻击?如何防范 CSRF 攻击?

CSRF 中文名为跨站请求伪造。

原理就是攻击者构造出一个后端请求地址,诱导用户点击或者通过某些途径自动发起请求。如果用户是在登录状态下的话,后端就以为是用户在操作,从而进行相应的逻辑。

举个例子,假设网站中有一个通过 GET 请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口

1
<img src="http://www.domain.com/xxx?comment='attack'" />

那么你是否会想到使用 POST 方式提交请求是不是就没有这个问题了呢?其实并不是,使用这种方式也不是百分百安全的,攻击者同样可以诱导用户进入某个页面,在页面中通过表单提交 POST 请求

如何防御

防范 CSRF 攻击可以遵循以下几种规则:

  1. GET 请求不对数据进行修改
  2. 不让第三方网站访问到用户 Cookie
  3. 阻止第三方网站请求接口
  4. 请求时附带验证信息,比如验证码或者 Token

SameSite

可以对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。

验证 Referer

对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。

Token

服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。

点击劫持

什么是点击劫持?如何防范点击劫持?

点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。

如何防御

X-FRAME-OPTIONS

X-FRAME-OPTIONS 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头就是为了防御用 iframe 嵌套的点击劫持攻击。

该响应头有三个值可选,分别是

DENY,表示页面不允许通过 iframe 的方式展示 SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示 ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示

JS 防御

对于某些远古浏览器来说,并不能支持上面的这种方式,那我们只有通过 JS 的方式来防御点击劫持了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<head>
<style id="click-jack">
html {
display: none !important;
}
</style>
</head>
<body>
<script>
if (self == top) {
var style = document.getElementById('click-jack')
document.body.removeChild(style)
} else {
top.location = self.location
}
</script>
</body>

以上代码的作用就是当通过iframe的方式加载页面时,攻击者的网页直接不显示所有内容了。

中间人攻击

什么是中间人攻击?如何防范中间人攻击?

中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。

通常来说不建议使用公共的 Wi-Fi,因为很可能就会发生中间人攻击的情况。如果你在通信的过程中涉及到了某些敏感信息,就完全暴露给攻击方了。

当然防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。HTTPS 就可以用来防御中间人攻击,但是并不是说使用了 HTTPS 就可以高枕无忧了,因为如果你没有完全关闭 HTTP 访问的话,攻击方可以通过某些方式将 HTTPS 降级为 HTTP 从而实现中间人攻击。

网页验证码是干嘛的,是为了解决什么安全问题。

区分用户是计算机还是人的公共全自动程序。可以防止恶意破解密码、刷票、论坛灌水;

有效防止黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。


title: jQuery

date: 2019-08-22 13:14:59

tags: javascript

categories:  # 这里写的分类会自动汇集到 categories 页面上,分类可以多级

  • JS # 一级分类
  • jQuery # 二级分类

选择器

jQuery 基本选择器 解释
$(‘*‘) 匹配所有元素
$(‘#id’) id 选择器
$(‘.class’) 类选择器
$(‘element’) 标签选择器
组合选择器 解释
$(‘E,F’) 多元素选择器,同时匹配 E 和 F
$(‘E F’) 后代选择器,用空格分隔,匹配 E 元素所有后代
$(‘E>F’) 子元素选择器,匹配 E 元素所有直接子元素
$(‘E+F’) 直接相邻元素,匹配 E 元素之后的相邻的同级元素 F
$(‘E~F’) 普通相邻元素(弟弟选择器),匹配 E 元素之后的同级元素 F(无论是否直接相邻)
$(‘class1.class2’) 匹配类名中既含有 class1 又含有 class2 的元素
基本过滤选择器 解释
$(‘E:first’) 所有 E 中的第一个
$(‘E:last’) 所有 E 中的最后一个
$(‘E:not(selector)’) 按照 selector 过滤 E
$(‘E:even’) 所有 E 中 index 是偶数
$(‘E:odd’) 所有 E 中 index 是奇数
$(‘E:eq(n)’) 所有 E 中 index 为 n 的元素
$(‘E:gt(n)’) 所有 E 中 index 大于 n 的元素
$(‘E:lt(n)’) 所有 E 中 index 小于 n 的元素
$(‘header’) 选择 h1~h6 元素
$(‘div:animated’) 选择正在执行动画效果的元素
内容过滤器 解释
$(‘E:contains(value)’) 内容包含 value 的元素
$(‘E:empty’) 内容为空的元素
$(‘E:has(F)’) 子元素中有 F 的元素,$(‘div:has(a)’):包含 a 标签的 div
$(‘E:parent’) 父元素是 E 的元素
可视化选择器 解释
$(‘E:hidden’) 所有被隐藏的 E
$(‘E:visible’) 所有可见的 E
属性过滤选择器 解释
$(‘E[attr]’) 含有属性 attr 的 E
$(‘E[attr=value]’) 属性 attr=value 的 E
$(‘E[attr !=value]’) 属性 attr!=value 的 E
$(‘E[attr ^=value]’) 属性 attr 以 value 开头的 E
$(‘E[attr $=value]’) 属性 attr 以 value 结尾的 E
$(‘E[attr *=value]’) 属性 attr 包含 value 的 E
$(‘E[attr][attr*value]’) 可以连用
子元素过滤器 解释
$(‘E:nth-child(n)’) E 的第 n 个子节点
$(‘E:nth-child(3n+1)’) E 的第 3n+1 个子节点
$(‘E:nth-child(even)’) E 的 index 为偶数的子节点
$(‘E:nth-child(odd)’) E 的 index 为奇数的子节点
$(‘E:first-child’) 所有 E 的第一个子节点
$(‘E:last-child’) 所有 E 的最后一个子节点
$(‘E:only-child’) 只有唯一子节点的 E 的子节点
表单元素选择器 解释
$(‘E:type’) 特定类型的 input
$(‘:checked’) 被选中的 checkbox 或者 radio
$(‘option:selected’) 被选中的 option

其他查找相关元素的方法

.eq(index), .get([index])

$('div').eq(3) //获取所有div中第4个jQuery对象

$('div')[2] 或者 $('div').get(2) //获取第三个的DOM对象

get()不写参数把所有对象转为 DOM 对象返回

兄弟元素获取

选择器 解释
.next([selector]) 没有写 selcetor,返回所有后面的兄弟元素.写了就返回满足条件的
.prev([selector]) 和上面相反,获取前面的兄弟元素
.nextAll([selector]) 获取所有后面的兄弟元素
.prevAll([selector]) 获取所有前面的兄弟元素
.siblings([selector]) 获取所有前后邻居的元素

父子元素获取

选择器 解释
.parent([selector]) 获取父元素,可选筛选器
.parents([selector]) 获取祖先元素,可选筛选器
.children([selector]) 获取子元素,可选筛选器
.find([selector]) 查找符合选择器的后代

筛选当前结果集

选择器 解释
.first() 获取当前结果集第一个对象
.last() 获取当前结果集最后一个对象
.filter(slector), .filter(function(index)) 筛选当前结果集符合条件的对象
.not(selector), .not(function(index)) 从当前结果集中移除指定元素
.is(selector), is(function(index)), is(dom/jqObj) 判断结果集中的元素,是否为一个选择器,DOM 元素,或者 jQuery 对象,如果这些元素至少一个匹配给定的参数,那么返回 true
.has(seletor), has(dom) 筛选匹配结果集中符合条件的后代元素

jQueryDOM 操作

创建元素

将 DOM 传入$方法即可返回一个 jQuery 对象

var obj = $('<div class="test"><p><span>Done</span></p></div>')

添加元素

方法 解释 示例
.append(content[,content]) / .append(function(index,html)) 可以添加多个内容,DOM 对象,字符串,jQuery 对象 ; ; $(‘p’).append(document.createTextNode(‘hello’))
.appendTo(target) 把对象插入到目标 target 尾部,可以是 selector,DOM 对象,字符串,元素集合,jQuery 对象(最后一个孩子) ; $(‘

Test

‘).appendTo(‘.inner’)
.prepend(content[,content]) / .prepend(function(index, html)) 向头部追加内容,内容添加到最前面(第一个孩子) $(‘.inner’).prepend(‘

Test

‘)
.prependTo(Target) 把对象插入到目标头部 $(‘

Test

‘).prependTo(‘.inner’)
.before([content][,content]) / .before(function) 在对象前面(不是头部,而是外面,和对象并列同级)插入内容(放到前面做邻居) $(‘.inner’).before(‘

Test

‘); ; $( “p” ).before( document.createTextNode( “Hello” ) )
.insertBefore(target) 把对象插入到 target 之前(不是头部,是同级)(做邻居)
.after([content][,content]) / .after(function(index)) 和 before 相反,在对象后面(不是尾部,而是外面,和对象并列同级)插入内容,参数和 append 类似 $( “.inner” ).after( “

Test

“ ); $( “p” ).after( document.createTextNode( “Hello” ) );
.insertAfter(target) 和 insertBefore 相反,把对象插入到 target 之后(同样不是尾部,是同级) $( “

Test

“ ).insertAfter( “.inner” ); $( “p” ).insertAfter( “#foo” );

删除元素

方法 解释 示例
.remove([selector]) 删除被选元素及其子元素 $(‘.div’).remove()
.empty() 清空被选元素内所有子元素 $(‘body’).empty()
.detach() .detach() 方法和.remove()一样, 除了 .detach()保存所有 jQuery 数据和被移走的元素相关联。当需要移走一个元素,不久又将该元素插入 DOM 时,这种方法很有用。 $(‘.div’).detach()

包裹元素

方法 解释 示例
.wrap(wrapElement) / .wrap(function(index)) 为每个对象包裹一层 HTML 结构,可以是 selector, element, HTML string, jQuery object $( “.inner” ).wrap( “
“ )
.wrapAll(wrappingElement) 把所有匹配对象包裹在同一个 html 结构里 $(‘.inner’).wrapAll(‘
)
.wrapInner(wrapingElement) 为每个匹配的对象包裹一层 HTML 结构 $(‘.inner’).wrapInner(‘
)
.unwrap 把 DOM 元素外的壳去掉 $(‘p’).unwrap()

html([string])

$('div').html()

$('div').html('123')

这是一个读写两用的方法,用于获取修改元素的 innerHTML

  1. 没有传递参数的时候,返回元素的 innerHTML
  2. 当传递一个 string 参数时,修改元素的 innerHTML 为参数值

text()

和 html 方法类似,操作的是 DOM 的 innerText 值

jQuery 属性操作

属性相关

.val([value])

这是一个读写双用的方法,用来处理 input 的 value 值,当方法没有参数时返回 input 的 value 值.

当传递一个参数时,方法修改 input 的 value 值为参数值.

1
2
$(".input").val();
$(".input").val("newValue");

.attr()

.attr(attributeName)

获取元素特定属性的值

var title = $('em').attr('title')

.attr(attributeName,value)/.attr(attributesJson)/.attr(attributeName,funtion(index,attr))

为元素属性赋值

$( "#greatphoto" ).attr( "alt", "Beijing" )

.removeAttr()

为匹配的元素集合中的每个元素移除一个属性

$('div').removeAttr('id')

.prop()/.removeProp()

这两个方法是用来操作元素的 property 的

CSS 相关

.css()

.css(propertyName) / .css(propertyNames)

获取元素 style 特定 property 的值

1
2
3
var color = $(this).css("background-color");

var styleProps = $(this).css(["width", "height", "color", "background-color"]);
.css(propertyName,value)/.css(propertyName,function(index,value))/ .css(propertiesJson)

设置元素 style 特定 property 的值

1
2
3
4
5
6
7
8
9
10
11
12
$("div.example").css("width", function (index) {
return index * 50;
});

$(this).css("width", "+=200");

$(this).css("background-color", "yellow");

$(this).css({
"background-color": "yellow",
"font-weight": "bolder",
});
.addClass(className) / .addClass(function(index,currentClass))

为元素添加 class,不是覆盖原 class,是追加,也不会检查重复

1
2
3
4
5
$("p").addClass("myClass yourClass");

$("ul li").addClass(function (index) {
return "item-" + index;
});
.removeClass([className])/.removeClass(function(index,class))

移除元素单个/多个/所有 class

1
2
3
4
5
$("p").removeClass("myClass yourClass");

$("li:last").removeClass(function () {
return $(this).prev().attr("class");
});
.hasClass(className)

检查元素是否包含某个 class,返回 true/false

1
$("#mydiv").hasClass("foo");
.toggleClass(className)

toggle 是切换的意思,方法用于切换,switch 是个 bool 类型值

1
2
3
4
5
6
7
8
9
<div class="tumble">Some text.</div>

//第一次执行
$( "div.tumble" ).toggleClass( "bounce" )
<div class="tumble bounce">Some text.</div>

//第二次执行
$( "div.tumble" ).toggleClass( "bounce" )
<div class="tumble">Some text.</div>

jQuery 常用方法

.each(fnction(index, Element))

遍历一个 jQuery 对象,为每个匹配元素执行一个函数

1
2
3
4
5
$("li").each(function (index) {
console.log(index + ":" + $(this).text());
});
//回调函数返回的是DOM对象,所以this需要加$变成jQuery对象才能使用.text()方法
//否则使用this.innerText方法

jQuery.each(collection, callback(indexInArray, valueOfElement))

一个通用迭代函数,可以迭代对象和数组.数组和类数组对象通过长度属性来迭代数字索引,从 0 到 length-1.

其他对象通过其属性名进行迭代.

上面 jQuery 可以用.each()`.

1
2
3
4
5
6
7
var obj = {
"fla" : "infla",
"duh" : " no duh"
}
$.each(obj, function(key, value){
console.log(key + ":" + value)
}

.map(callback(index, domElement))

通过一个函数匹配当前集合中的每个元素,返回一个包含新的 jQuery 对象

1
2
3
$(".div").map(function (i, ele) {
return this.id;
});

jQuery.extend([deep,]target[,ovject1][,objectN])

  1. 当提供两个或多个对象给$.extend(),对象的所有属性都添加到目标对象(target 参数).
  2. 如果只有一个参数提供给$.extend(),这意味着目标参数被省略,在这种情况下,jQuery 对象本身默认为目标对象.

这样我们可以在 jQuery 的命名空间下添加新功能.

var obj = $.extend({}, object1, object2)

object1,object2会被添加到{}中

如果第一个对象的属性本身是一个数组或对象,那它将完全用第二个对象相同的 key 重写一个属性.

这些值不会被合并.如果 true 作为第一个参数,那么会在对象上进行递归的合并.

.clone([withDataAndEvents])

.clone()方法深度复制所有匹配的元素集合,包括所有匹配元素,匹配元素的下级,文字节点.

$('.hello').clone().appendTo('.Goodbye')

.index()/.index(selector)/.index(element)

从给定集合中查找特定元素 index

  1. 没参数返回第一个元素 index
  2. 如果参数是 DOM 对象或 jQuery 对象,则返回参数在集合中的 index
  3. 如果参数是选择器,返回第一个匹配元素的 index,没有找到返回-1
1
2
var listItem = $(".bar");
console.log("Index:" + $("li").index(listItem));

.ready(handler)

当 DOM 加载完毕,执行.

下面两种等价

  • $(document).ready(handler)
  • $(handler)

jQuery 事件

.on(events[,selector][,data],handler(eventObject))

  1. events: 一个或多个空格分隔的事件类型和可选空间
  2. selector: 一个选择器字符串,用于过滤被选中的元素中能触发事件的后代元素.

如果选择器是 null 或者忽略,那么被选中的元素总能触发事件 3. data: 当一个事件被触发,要传递给事件处理函数的 event.data 4. handler(eventObject): 事件被触发时,执行的函数.

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
<div class="box">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<input id="ipt" type="text"><button id="btn">添加</button>
<div id="wrap"></div>

<script>
$('.box li').on('click',function(){
console.log(1)
var str = $(this).text()
$('#wrap').text(str)
})
//等同于
$('.box>ul>li').click(function(){
console.log(2)
var str = $(this).text()
$('#wrap').text(str)
})
//下面新增的元素是没有绑定事件的
$('btn').on('click',function(){
var value = $('#ipt').val()
$('.box>li').append('<li>' + value + '</li>')
})
//可以用事件代理
$('.box ul').on('click','li',function(){
var str = $(this).text()
$('#wrap').text(Str)
})
//上面代码换成原生js代码
document.querySelector('.box ul').addEventListener('click',function(e){
if(e.target.tagName.toLowerCase() === 'li'){
//do something
}
})
</script>

.one(events[,selector][,data],handler(eventObject))

同 on,绑定事件,但只执行一次

.off(events[,selector][,handler])

移除一个事件处理函数

$('.box li').off('click')

.trigger(eventType[,extraParameters])

根据绑定到匹配元素的给定的事件类型执行所有的处理程序和行为

1
2
3
4
$("#foo").on("click", function () {
console.log($(this).text());
});
$("#foo").trigger("click");

其他

事件

blur([[data],fn])

change([[data],fn])

click([[data],fn])

dblclick([[data],fn])

error([[data],fn])

focus([[data],fn])

focusin([data],fn)

focusout([data],fn)

keydown([[data],fn])

keypress([[data],fn])

keyup([[data],fn])

mousedown([[data],fn])

mouseenter([[data],fn])

mouseleave([[data],fn])

mousemove([[data],fn])

mouseout([[data],fn])

mouseover([[data],fn])

mouseup([[data],fn])

resize([[data],fn])

scroll([[data],fn])

select([[data],fn])

submit([[data],fn])

事件对象

eve.currentTarget

eve.data

eve.delegateTarget

eve.isDefaultPrevented()

eve.isImmediatePropag…()

eve.isPropagationStopped()

eve.namespace

eve.pageX

eve.pageY

eve.preventDefault()

eve.relatedTarget

eve.result

eve.stopImmediatePro…()

eve.stopPropagation()

eve.target

eve.timeStamp

eve.type

eve.which

jQuery 动画

基础

.hide([duration][,easting][,fn])

  • duration: 三种预定速度之一的字符串(“slow”,”normal”, or “fast”)或表示动画时长的毫秒数值(如:1000)
  • easing: 用来指定切换效果,默认是”swing”,可用参数”linear”
  • fn: 在动画完成时执行的函数,每个元素执行一次。

用于隐藏元素,没有参数等同于直接设置 display 属性

1
2
$(".target").hide();
//等同于$('.target').css('display','none')
1
2
3
$("#book").hide(300, "linear", function () {
console.log("Animation complete.");
});

.show([duration][,easing][,fn])

显示元素,用法类似 hide

.toggle([duration][,easing][,fn])

事件处理套件也有一个名为.toggle()方法。哪一个被调用取决于传递的参数的设置

用来切换元素的隐藏、显示,类似于 toggleClass,用法和 show、hide 类似

渐变

.fadeIn( [duration ] [, easing ] [, complete ] )

渐入效果

.fadeOut( [duration ] [, easing ] [, complete ] )

渐出效果

.fadeTo( duration, opacity [, easing ] [, complete ] )

调整匹配元素的透明度,方法通过匹配元素的不透明度做动画效果

1
2
3
$("#book").fadeTo("slow", 0.5, function () {
// Animation complete.
});

fadeToggle([speed,[easing],[fn]])

通过不透明度的变化来开关所有匹配元素的淡入和淡出效果,并在动画完成后可选地触发一个回调函数。

这个动画只调整元素的不透明度,也就是说所有匹配的元素的高度和宽度不会发生变化。

1
2
3
4
$("p").fadeToggle("fast", function () {
alert("Animation Done.");
});
//用200毫秒快速将段落淡入,之后弹出一个对话框

slideDown([speed],[easing],[fn])

下拉动画.这个动画效果只调整元素的高度,可以使匹配的元素以“滑动”的方式显示出来。

slideUp([speed,[easing],[fn]])

上浮动画,向上隐藏,在隐藏完成后可选地触发一个回调函数。

slideToggle([speed],[easing],[fn])

上下切换

动画队列

因为动画是异步,所以将动画执行函数放入回调函数中,等动画执行完毕再执行其他的.

但是这样写会形成回调地狱.又因为存在动画队列,可以按代码二方法执行会有同样效果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$box.hide(1000, function () {
$box.show(1000, function () {
$box.fadeOut("slow", function () {
$box.fadeIn("slow", function () {
$box.slideUp(function () {
$box.slideDown(function () {
console.log("动画执行完毕");
});
});
});
});
});
});
//等价于
$box
.hide(1000)
.show(1000)
.fadeOut()
.fadeIn()
.slideUp()
.slideDown(function () {
console.log("真的完毕了");
});

自定义动画

animate(params,[speed],[easing],[fn])

这个函数的关键在于指定动画形式及结果样式属性对象。

这个对象中每个属性都表示一个可以变化的样式属性(如“height”、“top”或“opacity”)。

注意:所有指定的属性必须用驼峰形式,比如用 marginLeft 代替 margin-left.

  • params:一组包含作为动画属性和终值的样式属性和及其值的集合
  • speed:三种预定速度之一的字符串(“slow”,”normal”, or “fast”)或表示动画时长的毫秒数值(如:1000)
  • easing:要使用的擦除效果的名称(需要插件支持).默认 jQuery 提供”linear” 和 “swing”.
  • fn:在动画完成时执行的函数,每个元素执行一次。

.finish

停止当前动画,并清除动画队列中所有未完成的动画,最终展示动画队列最后一帧的最终状态

.stop( [clearQueue] [,jumpToEnd])

停止当前正在运行的动画

AJAX

jQuery.ajax(url,[settings])

jQuery 底层 AJAX 实现.$.ajax()返回其创建的 XMLHttpRequest 对象。

大多数情况下你无需直接操作该函数,除非你需要操作不常用的选项,以获得更多的灵活性。

最简单的情况下,$.ajax()可以不带任何参数直接使用。

注意,所有的选项都可以通过$.ajaxSetup()函数来全局设置。

回调函数

如果要处理$.ajax()得到的数据,则需要使用回调函数。beforeSend、error、dataFilter、success、complete。

  • beforeSend 在发送请求之前调用,并且传入一个 XMLHttpRequest 作为参数。
  • error 在请求出错时调用。传入 XMLHttpRequest 对象,描述错误类型的字符串以及一个异常对象(如果有的话)
  • dataFilter 在请求成功之后调用。传入返回的数据以及”dataType“参数的值。

并且必须返回新的数据(可能是处理过的)传递给 success 回调函数。

  • success 当请求之后调用。传入返回后的数据,以及包含成功代码的字符串。
  • complete 当请求完成之后调用这个函数,无论成功或失败。传入 XMLHttpRequest 对象,以及一个包含成功或错误代码的字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$.ajax({
url: 'test.com',
type: 'GET',
datatype: json,
data: {
name: 'tom',
age: '12'
},
<!-- success: function(result){
console.log(result)
} -->
}).done(function(result){
console.log(result)
}).fail(function(jqXHR, textStatus){
consloe.log(textStatus)
})

url: 一个用来包含发送请求的 URL 字符串。

settings: AJAX 请求设置。所有选项都是可选的。

seting 中的设置
  1. async:默认设置下,所有请求均为异步请求(也就是说这是默认设置为 true )。

如果需要发送同步请求,请将此选项设置为 false 2. beforeSend:请求发送前的回调函数,用来修改请求发送前 jqXHR 对象,此功能用来设置自定义 HTTP 头信息

该 jqXHR 和设置对象作为参数传递 3. cache:如果设置为 false ,浏览器将不缓存此页面。

注意: 设置 cache 为 false 将在 HEAD 和 GET 请求中正常工作。

它的工作原理是在 GET 请求参数中附加”_={timestamp}” 4. context:这个对象用于设置 Ajax 相关回调函数的上下文。

默认情况下,这个上下文是一个 ajax 请求使用的参数设置对象 5. data:发送到服务器的数据。将自动转换为请求字符串格式。

GET 请求中将附加在 URL 后面,POST 请求作为表单数据 6. headers:一个额外的{键:值}对映射到请求一起发送。此设置会在 beforeSend 函数调用之前被设置;

因此,请求头中的设置值,会被 beforeSend 函数内的设置覆盖 7. type: 请求方式 (“POST” 或 “GET”),默认为 “GET”。

注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。

jQuery.get( [settings] ) / jQuery.post( [settings ] )

这两个方法专门用来处理 get 和 post 请求

1
2
3
$.get("test.html", function (data) {
$(".result").html(data);
});

jQuery.getJSON(url[,data][,success(data,textStatus,jqXHR)])

使用一个 HTTP GET 请求从服务器加载 JSON 编码的数据

范例

1
2
3
4
5
6
7
8
9
10
11
12
$.getJSON("ajax/test.json", function (data) {
var items = [];

$.each(data, function (key, val) {
items.push('<li id="' + key + '">' + val + "</li>");
});

$("<ul/>", {
class: "my-new-list",
html: items.join(""),
}).appendTo("body");
});

.load(url[,data][,complete(responseText,textStatus,XMLHttpRequest)])

从服务器载入数据并且将返回的 HTML 代码并插入至匹配的元素中

$('#result').load('ajax/test.html')

.serialize() / serializeArray()

将用作提交的表单元素的值编译成字符串,方法没有参数,使用标准的 URL-encoded 符号上建立一个文本字符串.

它可以对一个代表一组表单元素的 jQuery 对象进行操作,比如<input>, <textarea>, 和 <select>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<form id="holder">
<input type="text" name="a" value="1" />
<div>
<input type="text" name="b" value="2" id="b" />
</div>
<input type="hidden" name="c" value="3" id="c" />
<div>
<input type="checkbox" name="f" value="8" checked="true" />
<input type="checkbox" name="f" value="9" checked="true" />
</div>
</form>;

$("#holder").serialize(); //a=1&b=2&c=3&f=8&f=9

$("#holder").serializeArray();
/*
[
{name: 'a', value: '1'},
{name: 'b', value: '2'},
{name: 'c', value: '3'},
{name: 'f', value: '8'},
{name: 'f', value: '9'}
]
*/

serialize 和 serializeArray 都是针对 JQuery 对象(选中的 form 元素)进行操作,只是返回值格式不同而已。

这里特别要注意:这 2 个 API 只能操作 form,如果将 holder 改成 div,会发现不起作用

jsonp

1
2
3
4
5
6
7
8
9
function getBooks() {
$.ajax({
type: "get",
url: "http://test.com/bookservice.php",
dataType: "jsonp",
jsonp: "callback",
jsonpCallback: "displayBooks",
});
}

当然使用 jsonp 会在一定程度上造成安全性问题,如果请求的站点不是信任站点,那么可能会在返回的方法调用中包含一些恶意代码。

所以尽量向信任的站点发送请求。另外 xss 也经常会利用 jsonp 向站点注入恶意代码。