--愿你内心有种不灭的火焰,将你与别人区分开来--

0%

viewport与网站适配

0x00 viewport

通俗的讲,移动设备上的viewport就是设备的屏幕上能用来显示我们的网页的那一块区域,但实际情况下并不总是这样。

移动设备的屏幕尺寸比 PC 端的屏幕尺寸要小得多,所以,移动端的浏览器为了能让那些在 PC 端开发的网站被正常的显示,决定默认情况下把 viewport 设为一个较宽的值,我们暂且把这个浏览器默认的 viewport 叫做 layout viewport,这个 layout viewport 的宽度可以通过 document.documentElement.clientWidth 来获取(iphone6的 layout viewport 值为 980 px)。

此外,还需要一个 viewport 来代表浏览器可视区域的大小,我们这个 viewport 称为 visual viewport ,可以通过 window.innerWidth 来获取它的宽度值。

最后,还有一个能够完美适配移动端设备的 viewport 。所谓的完美适配指的是,首先不需要用户缩放和横向滚动条就能正常的查看网站的所有内容;第二,显示的文字的大小是合适,比如一段14px大小的文字,不会因为在一个高密度像素的屏幕里显示得太小而无法看清,理想的情况是这段14px的文字无论是在何种密度屏幕,何种分辨率下,显示出来的大小都是差不多的。我们把这个 viewport 叫做 ideal viewport,即是 理想 viewportideal viewport 的宽度等于移动设备的屏幕宽度。

ideal viewport 并没有一个固定的尺寸,不同的设备拥有有不同的 ideal viewport,目前,iphone6的 ideal viewport 宽度值是 375px

0x01 meta 媒体查询

我们可以通过 meta 标签对移动设备的 viewport (移动设备默认的是 layout viewport),进行控制。

1
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=2.0,minimum-scale=0.5,user-scalable=yes"/>

对 meta viewport content 中的属性解释如下:

  • width : 设置 layout viewport 的宽度,该值为一个正整数,当设置为字符串 device-width 时,意味着将浏览器的 viewport 设置为 ideal viewport
  • initial-scale :设置页面的初始缩放值
  • minimum-scale :用户所能进行的最小缩放值
  • maximum-scale :用户所能进行的最大缩放值
  • user-scalable :是否允许用户进行缩放,值为 yesno

这些属性可以同时使用,也可以单独使用或混合使用,多个属性同时使用时用逗号隔开。

此外,缩放是相对于 ideal viewport 来缩放的,缩放值越大,当前 viewport 的宽度就会越小,反之亦然。

CSS3 加入的媒体查询使得无需修改内容便可以使样式应用于某些特定的设备范围。

link 元素中应用媒体查询:

1
<link href="example.css" rel="stylesheet" media="(max-width:800px) and (min-width:375px) ">
  • max-width :当媒体可视区域的宽不大于该值时应用 example.css 样式

  • min-width: 当媒体可视区域的宽不小于该值时应用 example.css 样式

在 CSS 样式中使用媒体查询:

1
2
3
4
5
6
7
8
<style type="text/css">
@media screen and (max-width: 800px) and (min-width: 375px){
/*style*/
*{
background: red;
}
}
</style>

这个查询适用于宽度在 375px 和 800px 之间的屏幕 screen 。与此类似的媒体类型 tv 代表电视, handheld 代表手持设备,print 代表打印机。

其中,and 属于逻辑操作符合,此外,还有 not, only, or 等逻辑操作符, 并且 or 操作符和 逗号(,) 操作符的作用一样。

比如,想在最小宽度为 700 像素或是横屏的手持设备上应用一组样式:

1
2
@media (min-width: 700px), handheld and (orientation: landscape) { ... }
// (orientation: portrait) 表示竖屏设备

媒体查询要写在 所有的 CSS 样式的最后

0x02 rem

当在进行移动设备的布局时,我们将会经常使用的一个单位是 rem,而不再是 px

rem, em

remem 的参考对象都是 font-size 这个属性的值,不过 em 的参考对象是父级的 font-size 值,而 rem 的参考对象是根元素 <html> 元素的 font-size 值。

1em = 父级 font-size 大小,1rem = 根元素 font-size 大小

如果 html5 要适应各种分辨率的移动设备,应该使用 rem 这样的尺寸单位,下面是各个分辨率范围在 html上 设置 font-size 的代码:

1
2
3
4
5
6
7
8
html{font-size:10px}
@media screen and (min-width:321px) and (max-width:375px){html{font-size:11px}}
@media screen and (min-width:376px) and (max-width:414px){html{font-size:12px}}
@media screen and (min-width:415px) and (max-width:639px){html{font-size:15px}}
@media screen and (min-width:640px) and (max-width:719px){html{font-size:20px}}
@media screen and (min-width:720px) and (max-width:749px){html{font-size:22.5px}}
@media screen and (min-width:750px) and (max-width:799px){html{font-size:23.5px}}
@media screen and (min-width:800px){html{font-size:25px}}

reference

了解真实的『REM』手机屏幕适配
移动端web app自适应布局探索与总结

CSS 3D

0x00 transform 2D

我们先来看看在二维坐标系中的 transform 的作用效果。

transform 可以让元素进行移动translate,旋转rotate,缩放scale,变形skew

2D 中的旋转角为正时,将会沿顺时针旋转。

旋转

1
transform:rotate(50deg)

缩放

1
transform:scale(xNum,yNum)

倾斜

1
2
3
transform:skewX(angle)
transform:skewY(angel)
trasnform:skew(xangle,yangle)

平移

1
transform:tanslate(x,y)

0x01 CSS 3D

关于坐标系

传说从初中到高中到大学的课堂上,教材中所涉及的立体几何基本都是右手系。
关于左手系与右手系的关系,见下图。

坐标系

规定在右手坐标系中,物体旋转的正方向是右手螺旋方向,即**从该轴正半轴向原点看是顺时针方向(记住这点很重要,不然在之后的 CSS 3D 旋转属性中,你的世界会有种眩晕感,然后你的坐标系开始紊乱,然后你的宇宙就崩溃了。。。)

OK,我们借坐标系短暂回忆了下我们的青春,现在我们来看看值移动设备或PC中的坐标系是如何建立的,同样见下图,一目了然。

3D坐标系

在移动设备和电脑中 Y 轴是向下的。

0x02 transform 3D 属性

transform-origin

transform-origin 可以改变元素是旋转中心的位置。默认情况下,元素即绕三维物体会绕着自身的中心点旋转。rotate() 默认的旋转中心是(50%,50%)。

CSS3变形属性中旋转、缩放、倾斜都可以通过 transform-origin 属性重置元素的原点,但其中的位移 translate() 始终以元素中心点进行位移。

persective

需要知道的是,在设计 3D 效果时:

  • 只能选择透视方式,也就是近大远小的显示方式。
  • 镜头方向只能是平行Z轴向屏幕内,也就是从屏幕正前方向里看。
  • 初始状态下,所有元素都是放置在z=0的平面上。

perspective 属性让元素拥有了视野和视角的效果,而不是像以往的贴在屏幕上的平面,模拟了眼睛与物体之前的距离带来的远近视差效果。

通过 perspective(透视) 属性, 可以设置镜头到元素所在平面的距离,它会让东西看起来近处的大,远处的小。以此,从视觉上产生不同程度的3D效果。其默认值是设备的屏幕分辨率。所以不同设备的 perspective 默认值是不一样的。

设置 perserspective 有两种方式,

1
2
3
transform: perspective( 400px );
/**或者**/
perspective: 400px;

这两种写法,都触发了元素的3D行为,函数型的写法transform:perspective(400px) 适用于单个元素,会对每一个元素做3D视图的变换,而perspective:400px 的写法,需写在父元素上。

所以,perspective 并不影响当前元素的渲染,而是影响它的所有子元素。这也是它跟transform:perspective()方法的主要区别。

在 WebKit 浏览器里,使用 perspective 只要是它的祖先元素都行,但在火狐或IE里只能是直接父元素。

perspective 1000px

perspective 300px

此外,perspective-origin 属性规定了镜头在平面上的位置。镜头的默认位置是 对着 元素的中心点的。

backface-visibility

backface-visibility 属性可用于隐藏内容的背面。

transform-style

transform-style 属性是3D空间一个非常重要的属性,指定嵌套元素如何在3D空间中呈现。他主要有两个属性值:flatpreserve-3d

其中 flat 值为默认值,表示所有子元素都在2D平面呈现。而 preserve-3d 表示所有子元素在3D空间中呈现。

当对 舞台元素 (变形元素们的共同直接父元素)使用 transform-style:perserve-3d 时,便是为其中的所有的子元素声明了一个 3D 渲染空间,这样处于其中的子元素便会以 3D 的形态展现出来。

transform-style 属性是非继承的,即只对其直接子元素提供 3D 渲染空间,对于中间节点需要显式设定。

**如果需要使用 3D 模式,必须先为其直接父元素指定 transform-style:perserve-3d,并在任意祖先元素(包括直接父级元素)上增加 perspective 属性,有必要的好最好一并添加 perspective-origin 来指定透视点。

0x03 绘制 3D 图像

正方体

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Cubes</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link href="" rel="stylesheet">
<style type="text/css">
html,body{
perspective-origin: 50% 50%;
perspective:1000px;
height: 100%;
width: 100%;
background: #444;
}

.cube {
position: fixed;
top:25%;
left: 50%;
width: 200px;
height: 200px;
margin-left: -100px;

}

.container-3D{
transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg);
transform-style: preserve-3d;
animation: rotation 10s linear infinite;
}

@-webkit-keyframes rotation {
from{
transform: rotateY(0deg) rotateX(-180deg) rotateZ(-140deg);
}50%{
transform: rotateY(360deg) rotateX(-360deg) rotateZ(0deg);
}to{
transform: rotateY(360deg) rotateX(180deg) rotateZ(220deg);
}
}

@-webkit-keyframes plus{
from{ width: 200px; height: 200px;background-color: #B00;border-width: 1px;border-color: #000;border-radius: 0px; }
20%{ width: 200px; height: 200px;background-color: deeppink;border-width: 10px;border-color:#333;border-radius: 15px; }
40%{ width: 100px; height: 100px;background-color: yellowgreen;border-width: 12px;border-color: #444;border-radius: 20px; }
60%{ width: 100px; height: 100px; background-color: #B00; border-width: 30px;border-color: #fff;border-radius: 50px}
80%{ width: 180px; height: 180px; background-color: #0B0; border-width: 10px;border-color: #000; border-radius: 0px }
to{ width: 200px; height: 200px;background-color: #333;border-width: 1px;border-color: #123;border-radius: 10px; }
}


.face{
background: rgba(200,200,200,1);
width: 200px;
height: 200px;
position: absolute;
border:1px solid black;
transform-style: preserve-3d;
animation: plus 5s ease-in-out infinite alternate;
}

.face--front{
transform: translateZ(100px);
}

.face--back{
transform: translateZ(-100px);
}

.face--left{
transform: rotateY(-90deg) translateZ(-100px);
}

.face--right{
transform: rotateY(90deg) translateZ(-100px);
}

.face--top{
transform: rotateX(-90deg) translateZ(100px);
}

.face--bottom{
transform: rotateX(90deg) translateZ(100px);
}
</style>
</head>
<body>
<div class="cube container-3D">
<div class="face face--front">face--front</div>
<div class="face face--back">face--back</div>
<div class="face face--left">face--left</div>
<div class="face face--right">face--right</div>
<div class="face face--top">face--top</div>
<div class="face face--bottom">face--bottom</div>
</div>
</body>
<script type="text/javascript" src=" " ></script>
</html>

如上,我们为 html/body 设置 perspective 属性,使其成为舞台元素,而为 .container-3D 添加了 transform-style:preserve-3d 使其成为渲染 3D 空间的容器,为其直接子元素开启 3D 渲染环境。

3D container 元素的常用旋转动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.container-3D{
transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg);
transform-style: preserve-3d;
animation: rotation 10s linear infinite;
}

@-webkit-keyframes rotation {
from{
transform: rotateY(0deg) rotateX(-180deg) rotateZ(-140deg);
}50%{
transform: rotateY(360deg) rotateX(-360deg) rotateZ(0deg);
}to{
transform: rotateY(360deg) rotateX(180deg) rotateZ(220deg);
}
}

Canvas-快速入门

0x00 Canvas 初始化


使用 Canvas 元素必须为其设置宽度和高度属性,以确定可以绘制区域的大小。如果不添加任何样式或者不绘制任何图形,那么是看不到该元素的。

但是若是通过 CSS 样式来为其设置宽高属性的话,如果 CSS 的尺寸与 canvas 初始比例(canvas 默认初始 宽度: 300px高度 150 px ,既宽高比例为 2:1)不一致,它会出现扭曲。

创建 Canvas 元素,并通过 canvas.getContext() 方法获取其 2D 上下文。

1
2
3
4
5
6
7
8
// HTML
<canvas id="myCanvas" width="400" height="400">A drawing of something.</canvas>
// JS
var drawing = $("#myCanvas")
if (drawing.getContext){
// 确定浏览器是否支持 <canvas>
var context = canvas.getContext("2d")
}

如上,如果需要在画布上绘图,那么首先便需要获得绘图上下文。
然后,使用 canvas.toDataURL() 方法便可以获取在 canvas 元素上绘制的图像,它只接受一个参数,即是我们要指定图像的 MIME 类型。

获取 2D 上下文

1
2
3
if(drawing.getContext()){
var imgURL = drawing.toDataURL("image/png");
// 获取图像的数据 URL

##


0x01 描边和填充

使用 2D 上下文可以绘制简单的 2D 图形,比如矩形,弧线和路径。其两种基本绘图操作是填充(fillStyle)描边(strokeStyle)

strokeRect() & fillRect() 绘制矩形

可以使用 fillStyle() 属性来为通过 fillRect() 绘制的矩形填充颜色;使用 strokeStyle() 属性来为 strokeRect() 方绘制的矩形描边

1
2
3
4
5
6
7
8
9
10
11
12
var drawing = $("#myCanvas")
if (drawing.getContext){
// 检测浏览器是支持 canvas
var context1 = drawing.getContext("2d");
// 获得 2d 上下文
context1.fillStyle = "red"
context1.fillRect(40,20,50,50)
// 绘制一个矩形并填充 红色
context1.strokeStyle = "blue"
context1.strokeRect(10,10,50,50)
// 绘制一个矩形并描边为 蓝色
}

此外,可以使用 clearRect() 方法来清除指定区域。

0x02 绘制路径


一切形状的原始基础都是路径。在 Cavans 创建一个形状的首先需要的是创建新路径beginPath,再通过绘图命令,比如moveTo等在路径中绘制,然后关闭路径clostPath,最后填充颜色fill或描边stroke
!> 调用fill函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath函数。但是调用stroke时不会自动闭合。

moveTo()

设置笔触相对于画布左上角开始的起点位置,即是路径的起始点。

lineTo()

直线路径。

描边三角形


1
2
3
4
5
6
7
8
9
10
11
12
13
14
var drawing = $("#myCanvas")
if(drawing.getContext){
// 检测浏览器是支持 canvas
var context = drawing.getContext("2d");
// 获得 2d 上下文
context.beginPath()
context.strokeStyle = "green"
context.moveTo(50,200)
context.lineTo(200,100)
context.lineTo(100,50)
context.closePath()
context.stroke()
// 描边三角形
}

填充三角形

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
var drawing = document.querySelector("#drawing")
if(drawing.getContext){
//是否支持 canvas
context = drawing.getContext("2d")
// 获得 2d 上下文
context.beginPath()
// 开始绘画
context.fillStyle = "#333"
// 填充色为 #333
context.strokeStyle = "deeppink"
// 描边颜色为 deeppink
context.lineWidth = "20"
// 线框为 20 px
context.moveTo(150,150)
// 起始触点 (150,150)
context.lineTo(150,300)
context.lineTo(300,225)
// 绘制一个三角形
context.closePath()
// 闭合路径
context.stroke()
// 描边
context.fill()
// 填充
}

0x03 圆


arc() & arcTo()

arc(x,y,radius,startAngle,endAngle,clockwise): 画一个以(x,y)为圆心,以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。
!> arc() 函数中的角度单位是弧度,不是度数。角度与弧度的js表达式: radians=(Math.PI/180)*degrees

##

描边圆形


1
2
3
4
5
6
7
8
for(var i=0;i<6;i++){
for(var j=0;j<6;j++){
context.strokeStyle ='rgb(0,' + Math.floor(255-42.5*i) + ',' + Math.floor(255-42.5*j) + ')'
context.beginPath()
context.arc(50+60*i,50+60*j,30,0,Math.PI/180*360)
context.stroke()
}
}


参考 MDN 官网。

填充圆形


描边填充圆形,并使用globalAlpha, 设置其透明度为0.3

1
2
3
4
5
6
7
8
9
context.beginPath()
context.strokeStyle = "deeppink"
context.fillStyle ="#333"
context.globalAlpha = "0.3"
context.lineWidth = "20"
context.arc(450,300,100,0,Math.PI/180*300)
context.closePath()
context.stroke()
context.fill()

0x04 渐变


Canvas 支持的渐变效果包括线性(createLinearGradient())和径向(createRadialGradient())渐变,并使用addColorStop() 为其指定渐变颜色。

strokeStylefillStyle 属性都可以接 canvasGradient 对象。

渐变颜色 addColorStop()

addColorStop(position,color) 中第一参数 position 表示颜色出现在渐变中的相对位置。

线性渐变 createLinearGradient()

createLinearGradient(x1,y1,x2,y2),其所接收的四个参数,分别代表渐变的起点和终点。

1
2
3
4
5
6
7
8
9
10
context.beginPath()
var lineGradient1 = context.createLinearGradient(100,200,100,400)
lineGradient1.addColorStop(0.5,"green")
lineGradient1.addColorStop(1,"red")
context.strokeStyle = lineGradient1
context.lineWidth = "20"
context.moveTo(100,200)
context.lineTo(100,400)
context.closePath()
context.stroke()

径向渐变 createRadialGradient()

createRadialGradient(x1,y1,r1,x2,y2,r2) 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。

1
2
3
4
5
6
7
8
// 径向渐变
var radGrad = context.createRadialGradient(0,150,40,0,140,90)
radGrad.addColorStop(0,'#00C9FF')
radGrad.addColorStop(0.8,'#00B5E2')
radGrad.addColorStop(1,'rgba(0,201,255,0)')
// 画图形
context.fillStyle = radGrad
context.fillRect(0,0,150,150)

0x05 绘制文本


文本样式

  • font:这个字符串使用和 CSS 属性相同的语法. 默认的字体是 10px sans-serif
  • textAlign:文本对齐方式,可选值:start,end,left,right,center
  • textBaseline: 基线对齐方式,可选值:top,middle,bottom

fillText(text,x,y,[,maxWidth])

在指定的(x,y)位置填充指定的文本,绘制的最大宽度(可选).

strokeText(text,x,y,[,maxWidth])

在指定的(x,y)位置绘制空心文本,绘制的最大宽度(可选).

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
var linGrad = context.createLinearGradient(50,50,400,200)
linGrad.addColorStop(0.2,"red")
linGrad.addColorStop(0.7,"deeppink")
// 设置渐变
var str = "to be or not to be "
context.beginPath()
context.font = "60px 宋体"
// 设置文字格式 必需
context.textAlign = "left"
// 设置文字对齐方式 必需
context.textBaseline = "middle"
// 设置文字基线 必需
context.shadowColor = "#333"
context.shadowOffsetX = 10;
context.shadowOffsetY = 10;
context.shadowBlur = 10;
context.closePath()
// 闭合路径
context.fillStyle = linGrad
// 设置 fillStyle
context.strokeStyle = linGrad
// 设置 strokeStyle
context.fillText(str,50,50,400)
// 填充文字
context.strokeText(str,50,100,400)
// 描边文字
console.log(context.measureText(str))//width:570

measureText() 方法,将返回一个 [TextMetrics
]对象的宽度、所在像素,这些体现文本特性的属性。

0x06 Using Images


canvas 强大的特性还以使我们对图像进行操作处理。
当然,在对图像进行操作之间必然要引入图像资源,canvas 支持多种不同的图像资源引入方式,这里只了解常用的两种方式:使用 Image 对象或 <img> 标签 和 引用同一页面中的另一画布作为图像资源.

drawImage(source,x,y)

图像资源,以及在画布中的起始位置

drawImage(source,x,y,width,height)

图像资源,在画布中的起始位置,并以指定的宽度和高度显示在画布中。以次实现图像的缩放效果。

1
2
3
4
5
6
7
var isImage = new Image;
isImage.src = "Koala.jpg"
isImage.onload = function(){
// do drawingImage statement
context.drawImage(isImage,60,60,400,400)
//五个参数时,代表图片在画布中显示的起始点和图片显示的宽高
}

drawImage(source, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

当使用九个参数时,便是需要对图像进行切片处理了。其中sx,sy,sWidth,sHeight 规定要在图像源中取得的切片位置和切片大小;dx,dy,dWidht,dHeight 表示该切片在画布中显示的起始位置
和大小。

1
2
3
4
5
var isImage = new Image;
isImage.src = "Koala.jpg"
isImage.onload = function(){
context.drawImage(isImage,300,300,400,300,100,100,200,200)
}

createPattern()

我们可使用 createPattern() 方法来规定图像显示的方式,none,repeat,repeat-x,repeat-y

1
2
3
4
5
6
7
8
var isImage = new Image;
isImage.src = "Koala.jpg"
isImage.onload = function(){
//图片是否平铺
var imgs = context.createPattern(isImage,"repeat-x")
context.fillStyle = imgs
context.fillRect(0,0,700,500)
}

0x7 画布裁切 clip()

1
2
3
4
5
6
7
8
9
var isImage = new Image;
isImage.src = "Koala.jpg"
isImage.onload = function(){
context.drawImage(isImage,0,0,400,400)
}
//画布裁切 clip()
context.arc(230,230,170,0,Math.PI/180*360)
context.closePath()
context.clip()

0x8 画布方法


save() 和 restore()

saverestore方法是用来保存和恢复canvas状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

1
2
3
4
5
6
7
8
9
10
11
12
13
context.beginPath()
context.fillRect(50,50,150,150)
// 使用默认设置绘制一个矩形
context.save()
// 保存默认配置下的绘画状态
context.fillStyle = "deeppink"
// 设置一个新的绘画状态
context.fillRect(65,65,120,120)
// 使用新的绘制状态绘制一个矩形
context.restore()
// 恢复到默认绘制状态
context.fillRect(80,80,90,90)
// 同样,使用默认状态绘制一个矩形

restore() 恢复的是离它最近的 save() 之上 所保存的状态。

translate()

transltae(x,y) 方法用 移动 canvas 原点。

1
2
3
context.fillRect(50,50,100,100)
context.translate(100,100)
context.fillRect(50,50,100,100)

scale(x,y)

scale(x,y) 缩放,其所接收的两个参数分别代表在 x 的缩放因子和在 y 轴的缩放因子。

1
2
3
4
5
context.scale(1.5,1.5)
context.fillRect(50,50,100,100)
context.translate(150,150)
context.scale(0.5,0.5)
context.fillRect(50,50,100,100)

rotate()

rotate(angle) 只接受一个参数,即旋转的角度,它是顺时针方向的,与 arc() 同样是以 弧度 为单位的值。

1
context.rotate(Math.PI/180*deg)

scale 也好,translate,rotate() 也好,所有的样式和变形命令都应该写在填充和描边命令之前。

0x9 画一个五角星


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var r = 200
context.translate(200,200)
context.beginPath()
context.moveTo(r,0)
for(var i=0;i<9;i++){
context.rotate(Math.PI/180*36)
if(i%2 == 0){
context.lineTo(r/(Math.cos(Math.PI/180*36)*2)*0.7,0)
}else{
context.lineTo(r,0)
}
}
context.closePath()
context.fill()
}

reference

Canvas中特定图形绑定事件

CSS 字体排版

0x01 连字符断行

CSS3 添加了一新的属性,hyphens,它有三个值,nome,normal,auto。其作用是我们可以在任何时候手工插入软连字符,来实现断词折行的效果。只需要使用hyphens:auto就可以了。

0x02 插入换行

下面将会使用自定义列表来做一个 Contact 模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//HTML
<div id="contact">
<dl>
<dt>Name:</dt>
<dd>Jack</dd>

<dt>Email:</dt>
<dd>onejustone404@gmail.com</dd>
<dd>0nejust0one@gmail.com</dd>

<dt>Location:</dt>
<dd>BeiJing</dd>
</dl>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dt,dd {
display: inline;
margin:0;
}

dd {
margin: 0;
font-weight: bold;
}

dd+dt::before {
/*给每一个 dt 之前有 dd 的 dt 头部添加换行符*/
content: '\A';
/* \A 相当于换行符*/
white-space:pre;
/*保留源代码中的空白符和换行符*/
}

dd + dd:before {
/*在每个前面有 dd 的 dd 的头部插入逗号*/
content: ',';
margin-left: -0.25em;
/*使用负 margin 去除多个连续的 dd 之间的空白符*/
}

CSS 形状

0x00 border-radius

border-radius 有一个鲜为人知的属性,它可以单独指定水平和垂直半径,只要用斜杠(/) 分隔这两个值即可。此外,它不仅可以接受数值单位,还可以接受百分比值。而这个百分比会基于元素的尺寸进行解析。

所以,要创建一个自适应的椭圆,只需将两个方向(水平和垂直)的半径都设置为 50%

1
2
3
4
5
6
#box{
width:400px;
height:200px; border-radius: 50% / 50%;
}
// or 进一步简化
border-radius: 50%;

椭圆

大值特性和等比例特性

此外,border-radius 还有两个特性:大值特性等比例特性

大值特性

也就是值很大的时候,只会使用能够渲染的圆角大小渲染。大值特性相对于元素自身的 width ,height而言。

1
2
3
4
5
6
7
8
9
10
#box {
width: 200px;
height: 200px;
background: deeppink;
border-radius: 50%;
/*border-radius:100%*/
/*border-radius: 100px*/
/*border-radius:200px;*/
/*border-radius: 400px*/
}

如上,下面的渲染结果都是一样的,即最大渲染半径只能是 50%,100px;

border-radius的大值特性

等比例特性

等比例特性:水平半径和垂直半径的比例是恒定不变的。等比例特性相对设置的 border-radius 参数的比例而言的

1
2
3
4
5
6
7
#box {
width: 200px;
height: 300px;
background: deeppink;
border-radius: 300px 0 0 0/300px 0 0 0;

}

等比例特性

元素占据宽度200像素,高度300像素。所以,根据大值特性,水平方向的300像素只能按照200像素半径渲染;再根据等比例特性,虽然垂直方向理论上的最大半径是300像素,但是受制于当初设定的300px300px的1:1比例,垂直方向最终渲染的半径大小也是200像素。于是,我们最后得到的只是一个200像素200像素的圆弧。

最后值得注意的是,border-radius 属性中前面的方位关键字和后面的半径方位是不匹配.它们的关系是:border-垂直-水平-radius: 水平 垂直

OK,了解上面的东西以后我们可以开始使用 border-radius 来实现许多形状了,比如:

半椭圆

1
2
3
4
5
6
#box {
width: 200px;
height: 300px;
background: deeppink;
border-radius: 50% / 100% 100% 0 0 ;
}

半椭圆

四分之一椭圆

1
2
3
4
5
6
#box {
width: 200px;
height: 300px;
background: deeppink;
border-radius: 100% 0 0 0;
}

四分之一椭圆

0x01 平行四边形

我们很容易想到对矩形使用 skew() 变形属性来达到我们想要的效果,但是,这样势必导致其元素中的内容也会跟着被拉伸,于是,我们想到使用伪元素。

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
#box {
width: 100px;
height: 40px;
margin: 100px auto;
margin-left:400px;
text-align: center;
line-height: 40px;
/*其它文字,颜色,内边距等样式...*/
position: relative;
/*设置宿主元素为相对定位*/
}

#box:before {
/*使用伪元素来生成一个矩形*/
content: '';
position: absolute;
/*设置伪元素为绝对定位*/
left:0;
top:0;
right:0;
bottom: 0;
/*设置伪元素所有偏移量为0,使其自动继承宿主元素尺寸*/
z-index: -1;
/*将伪元素堆叠层次至于宿主元素之后*/
background: deeppink;
transform:skewX(45deg);
/*使用skewX()变形*/
border:1px solid red;
}

伪元素生成平行四边形

这技巧的关键在于,利用伪元素及其定位属性产生了一个方块,然后对伪元素设置样式,并将其放置在宿主元素的下层。

CSS 背景与边框

0x00 半透明边框

背景知识 RGBA/HSLA 颜色

在CSS3里我们可以使用RGBA和HSLA两种色彩模式,二者均可以用来在设置颜色的同时指定其它透明度。RGBA指的是“红色、绿色、蓝色和Alpha透明度”,而HSLA则代表“色调、饱和度、亮度和Alpha透明度”。

在RGBA模式里,前三个参数分别是红色、绿色和蓝色的强度值,取值从 0~255 或 0%~100% (最常见的是 0~255, 而非百分数形式)。而在HSLA模式里,前三个参数则分别代表色调( 0~360 )、饱和度( 0%-100% )和亮度( 0%~100% )。RGBA和HSLA第四个参数都是透明度,取值从0(完全透明)到1(完全不透明)。

CSS3仍有opacity属性,但它的作用是使整个元素都半透明,包括前景内容,而不仅是背景。

解决方案

需要知道的是,在默认情况下,背景会延伸到边框所在区域的下层。所以即使我们给边框设置了半透明的效果,那么从视觉上也是无法分辨的。所以,如果我们不希望背景侵入边框所在的范围,就需要使用到 CSS3 的 background-clip 背景切割属性,将它的值设置为 padding-box

1
2
3
border: 10px solid hsla(0%, 0%,100%,.5);
background:white;
background-clip:padding-box;

0x01 多重边框

box-shadow

不为人知的是,box-shadow 还可以接受第四个参数(称为”扩展半径”),通过指定正值或者负值,可以让投影面积加大或者减小。

一个正值的扩展半径加上两个为零的偏移量以及为零的模糊值,得到的投影其实就像是一道实线边框了,在加上 box-shadow 的最大好处,可以支持逗号分隔发法,那么我们便可以为其创建任意数量的投影了。

1
2
3
4
5
6
div{
height: 200px;
width: 200px;
background: yellowgreen;
box-shadow: 0 0 0 10px #655,0 0 0 15px deeppink, 0 2px 5px 15px rgba(0,0,0, 0.6);
}

多重边框+投影

outline

有时当我们只需要两层边框的时候,便可以使用 outline 属性来产生外层的边框,这种方案会变得非常灵活,而不同于 box-shadow 只能模拟实现边框。

1
2
3
4
5
6
7
div{
height: 200px;
width: 200px;
border: 20px solid #655;
border-radius: 10px;
outline: 5px dashed deeppink;
}

outline

描边的另一属性 outline-offset 还可以控制它更元素边缘之间的间距,这个属性可以接受负值。

1
2
3
4
5
6
7
8
div{
height: 200px;
width: 200px;
border: 20px solid #655;
border-radius: 10px;
outline: 5px dashed deeppink;
outline-offset: -25px;
}

outline-offset

但是,IE8 以下的并不支持 outline-offset 属性。

0x02 背景定位

背景定位

有时,我们希望背景图片与容器的边角之间留出一定的空隙(类似内边距的效果),在 CSS2 的时代要实现这一点是很麻烦的。但是在 CSS3 的时代 background-position 属性已经得到了很好的扩展,并且当结合 background-origin 属性使用时,将发挥出更大的创造力。

在 CSS3 中,background-position 允许我们指定背景图片距离任意角的偏移量,只需我们在偏移量前指定关键字就好了。

需要知道的是,background-position 在默认情况下是以 padding-box 为基准的,不过,我们可以使用 CSS3 中一个新的属性 background-origin 来改变这种默认行为。background-origin 默认值同样为 padding-box,其它可以接受的值是,content-box 和 border-box。

1
2
3
4
5
6
7
8
9
10
#box{
width:500px;
height:500px;
border:20px solid rgba(0,0,0,0.5);
background: url(img/adver2.jpg) no-repeat ;
/*background-clip:content-box; */
background-position: right 20px bottom 10px;
background-origin: content-box;
padding:40px;
}

如此,我们在 background-position 中使用的边角关键字将会以内容区的边缘作为基准。

0x03 边框内圆角

一个灵活的方法是使用两个嵌套的 div 来实现边框内圆角的效果。

1
2
3
4
5
6
7
8
9
10
#box{
background:#655;
padding:0.8em;
}

#subBox {
background: tan;
padding:0.8em;
border-radius:0.8em;
}

边框内圆角

0x04 条纹背景

背景知识 CSS3 渐变中的百分比

在 CSS 渐变属性中使用百分比的作用是指某个颜色距离起点的起始位置。默认的渐变样式为从上往下,所以当某个颜色值设置了百分比后,便会从距离顶端相关的距离(百分比计算)开始填充实色。而渐变是也有空间占比的,渐变过渡区的占比为总的空间(高度或宽度)减去上下两个着色块空间占比剩下的空间。

红色 30% 橙色70%的渐变过渡占比为

图片

但若是前面有比当前的颜色值百分比大的,会自动将当前颜色值的百分比设置为前面颜色中的最大百分比值。

此外,默认情况下,还会根据颜色的个数来为每个颜色设置百分比,最后一个颜色的百分比值就是100%,而起始的值就是0%,中间如果再有多个颜色值,则根据100/(个数-1)平均下去。

如此,便可以做出一个简单的多重颜色线条的背景来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#box{
width:400px;
height:200px;
background:linear-gradient(
red 0,
red 14.3%,
orange 0,
orange 28.6%,
yellow 0,
yellow 42.9%,
green 0,
green 57.2%,
blue 0,
blue 71.5%,
indigo 0,
indigo 85.8%,
purple 0,
purple 100%);
}

颜色要设置两次,是因为每个颜色需要一个起始着色点,然后还需要将两个颜色之间的渐变过渡区域覆盖为实色,消除过度效果。

图片

水平条纹

渐变是一种由代码生成的图像,我们能想对待其他任何背景图像那般来对待他,比如对其使用 background-size 来调整其大小。

1
2
3
4
5
6
7
8
9
10
div{
width:200px;
height: 200px;
background:linear-gradient(
#fb3 50%,
#58a 0
);
background-clip:padding-box;
background-size: 20px 100%;
}

图片

### 垂直条纹

1
2
3
4
5
6
7
8
9
10
11
div{
width:200px;
height: 200px;
background:linear-gradient(
to right,/*or 90deg*/
#fb3 50%,
#58a 0
);
background-clip:padding-box;
background-size: 100% 20px;
}

图片

斜向条纹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
div{
width:200px;
height: 200px;
background:linear-gradient(
45deg,
#fb3 0,
#fb2 25%,
#58a 0,
#58a 50%,
#fb3 0,
#fb3 75%,
#58a 0,
#58a 100%
);
background-clip:padding-box;
background-size: 20px 20px;
}

如果我们需要为背景添加斜向条纹,那么便需要为贴片( 20px,20px)设置完整的色标。不幸的是,这种方法并不完美,当我们尝试改变渐变的角度时,看起来会很糟糕。幸运的是,还有更好的方法来创建斜向条纹,即 repeating-linear-gradientrepeating-radial-gradient,循环式的重复渐变。

如此,便再也无须担心如何去创建无缝拼接的贴片。并且,我们会直接在渐变的色标中指定长度,而不是原来的 bakcground-size ,这里的长度是直接在渐变轴上进行度量的,它直接代表了条纹自身的宽度,对渐变来说就是以整个元素的范围进行填充。

1
2
3
4
5
6
7
8
9
10
11
div{
width:200px;
height: 200px;
background:repeating-linear-gradient(
45deg,
#fb3 0,
#fb2 15px,
#58a 0,
#58a 30px
);
}

图片

需注意的是在这个方法中,如果我们想要创建双色条纹,那么便需要使用四个色标才行。

同色系条纹

1
2
3
4
5
6
7
8
9
10
11
12
div{
width:200px;
height: 200px;
background: deeppink;
background-image: repeating-linear-gradient(
30deg,
hsla(0,0%,100%,0.3),
hsla(0,0%,100%,0.3) 15px,
transparent 0,
transparent 30px
);
}

我们首先为其指定了一个主色系的背景颜色,然后把半透明白色的条纹叠加在主色系背景之上得到浅色条纹。

图片

JS对象番外篇(2)继承的多种方式

0x01 基本继承模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType(){
this.property = true;
}

SuperType.prototype.getSuperValue = function (){
return this.property;
};

function SubType(){
this.subproperty = false;
}

SubType.prototype = new SuperType();

SubType.prototype.getSubVaule = function(){
return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSubVaule()); //false
console.log(instance.getSuperValue()); //true

如上,我们通过将 超类的实例赋值给子类的原型实现了从超类到子类的继承。

然后,又通过将子类的实例赋值给 instance的原型 实现了从子类到 instance 的继承。

需要知道的是,此时 instance 的 constructor 指向的是 SuperType;因为子类的原型指向的是超类的原型对象,而这个原型的 constructor 属性指向的是 超类。

同样,在我们使用原型继承的时不能使用对象字面量创建原型方法,这样会重写原型。

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依此层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

0x01 Combination Inheritance

组合继承(Combination Inheritance) 有叫伪经典继承,其结合了原型链和构造函数两者之长。其思想是使用原型链实现对原型属性和方法的继承,而通过构造函数实现对实例属性继承。

如此,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>

</body>
<script type="text/javascript">
function SuperType (name){
this.name = name;
this.colors = ["red", "green", "blue"];
}

SuperType.prototype.sayName = function (){
console.log(this.name);
}

function SubType (name, age){
//继承属性
SuperType.call(this, name);
//定义 SubType 自己的属性
this.age = age;
}

//继承方法
SubType.prototype = new SuperType();
//定义 SubType 自己的方法
SubType.prototype.sayAge = function (){
console.log(this.age);
}

//创建 instance1 实例,并继承自 SubType
var instance1 = new SubType("Jack", 89);
instance1.colors.push("black");
console.log(instance1.colors); //red,green,blue,black
instance1.sayName(); //jack
instance1.sayAge(); //89

var instance2 = new SubType("Marge", 23);
console.log(instance2.colors); //red,green,blue
instance2.sayName(); //Marge
instance2.sayAge(); //23
</script>
</html>

组合继承模式避免了原型链和借用构造函数的缺陷,融合了它们的优点,称为 JS 中最常用的继承模式。

0x02 寄生组合继承

所谓寄生组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思想是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一副本而已。本质上就是使用寄生继承来继承超类型的原型,然后再将结果指定给子类型的原型。

继承组合继承是引用类型最理想的继承方式。

1
2
3
4
5
function inheritPrototype (subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; // 指定对象
}

如上,inheritPrototype() 函数实现了继承组合继承最简单的形式。

这个函数接收两个参数: 子类型构造函数和超类型构造函数。首先在函数内部创建超类型原型的一个副本。然后为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 consructor 属性。最后将新创建的对象赋值给子类型的原型。

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
 function inheritPrototype (SubType, SuperType){
var prototype = Object(SuperType.prototype); //创建对象
prototype.constructor = SubType; //增强对象
SubType.prototype = prototype; // 指定对象
}

function SuperType(name){
this.name = name;
this.colors = ["red", "green", "blue"];

}

SuperType.prototype.sayName = function (){
console.log(this.name);
}

function SubType(name, age)
{
SuperType.call(this, name);
this.age = age;
}


inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function (){
console.log(this.age);
}

var instance = new SubType("jack");
instance.sayName();

JS对象番外篇(1)创建对象的多种方式

0x00 JS 对象

对象在 JS 中被称为引用类型的值(在其它语言中,对象是类的实例)。
JS 没有类的概念,至于对象,我们可以想象为散列表,一些键值对的组合,其值可以是数据或者函数。

在物理世界中对象是指在内存中可以被标识符引用的一块区域。

我们创建一个对象的时候实际上是在内存中开辟了一块空间,而对于这个对象(这块空间),可以使用任何的标识符去 引用(reference)[也可以是指向] 它。

1
2
3
4
5
6
7
8
9
10
11
12
13
var o = {
a: {
b:2
}
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o

var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1; // 现在,“这个对象”的原始引用o被o2替换了

var oa = o2.a; // 引用“这个对象”的a属性
// 现在,“这个对象”有两个引用了,一个是o2,一个是oa

0x01 使用 Object 创建对象

创建自定义对象最简单的方法就是创建一个 Object 实例,然后为其添加属性和方法。

1
2
3
4
5
6
7
var person = new Object();
person.name = "Jack";
person.age = "29";

person.sayName = function () {
alert(this.name);
}

0x2 字面量创建对象

除了使用 Object 实例化一个对象外,还可以使用对象字面量语法创建一个对象

1
2
3
4
5
6
7
var person = {
name: "Jack",
age: "29",
sayName: function () {
alert(this.name);
}
};

0x03 工厂模式

工厂模式抽象了创建具体对象的过程,从而使用函数来封装以特定接口创建对象的细节。

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson (name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName =function (){
alert(this.name);
};
return o;
}

var person1 = createPerson("jack", 27, "Student");

函数 createPerson() 能够根据接收的参数来创建一个包含必要信息的对象。
我们可以无数次的调用这个函数去创建一个对象。

工厂模式的问题

虽然工厂模式解决了创建多个相似对象的问题,也在很大程度上较少了代码量,但是工厂模式却没有解决对象识别问题。

0x03 构造函数模式

Object 构造函数或者字面量可以创建单个的对象,但是使用同一接口创建很多对象时,会产生大量重复代码,即使使用 工厂模式 可以解决创建多个相似对象的问题,但没法解决对象识别问题(即是如何知道一个对象的类型)。因此一种基于工厂模式的变种出现了,构造函数模式

1
2
3
4
5
6
7
8
9
10
function Person (name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function (){
alert(this.name);
};
}
var person1 = new Person("Jack", 29, "teacher");
var person2 = new Person("Macl", 28, "studnet");

如上,首先我们并没有显示的去创建对象,而是 new 了一个 Person() 构造函数( 为了与普通函数区分开来,构造函数首字母大写,构造函数没有 return 语句),创建了一个 Person 实例。

任何函数,只要通过 new 操作符来调用,它就可以作为构造函数;而任何函数,如果不使用 new 调用,那它与普通函数并没有什么不同。

构造函数模型的问题

使用构造函数的问题即是,每个方法都要在每个实例上重新创建一遍。比如,person1 和 person2 都用到了同一个函数,sayName()。但是那两个函数不是同一个 Funciton 实例。

当然,我们可以将构造函数内部定义的函数提取处理,在其外部从新定义,从而让其成为一个全局函数,这样 person1 和 person2 就可以共享在全局作用域中定义的同一个函数 sayName()。

但是新的问题有出现了,在全局作用域中定义的函数实际上只能被某个对象调用,更重要的是,当对象需要定义很多方法时,那么就要定义很多个全局函数,这样我们自定义的引用类型变丝毫没有封装性可言。

0x04 原型模式

在JS中每个对象有拥有一个原型对象,所以在每个对象内部都有一个指向其原型对象的指针(或者属性,这个属性叫原型属性(prototype),比如我们所创建的所有自定义对象的原型属性都会指向 Object 对象,而object 对象的原型对象是null。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person () { }

Person.prototype.name = "jack";
Person.prototype.age = 29;
Person.prototype.job = "teacher";
Person.prototype.sayName = function (){
alert(this.name);
}

person1 = new Person();
person2 = new Person();

alert(person1.sayName == person2.sayName); // true

如上,将 sayName() 方法和所有属性直接添加到了 Person 的 prototype 属性中,构造函数变成了一个空函数。虽然我并没有在Person 构造函数中定义sayName() 方法,但是由构造函数创建出来的新对象实例却拥有了 sayName() 方法。而这正是因为 person 对原型对象的特殊引用。

原型对象中有一个 constructor 属性指向包含 prototype 属性的函数

当为对象实例添加一个与对象属性同名的属性时,实例中的这个属性就会屏蔽原型对象的同名属性。但是,也可以使用 delete 操作符完全删除实例属性,从而能够重新访问原型中的属性。

封装的原型

我们可以使用一个包含所有属性和方法的对象字面量来重写整个原型对象,这样不但可以减少代码量,也更好的从视觉上进行了封装。但是一旦重写原型对象,那么 constructor 属性不再会指向 Person 而是指向 Object,这意味着,重写原型对象便切断了现有原型与任何之前已经存在的对象实例之间的联系。

所以我们需要将其设置回正确的值

1
2
3
4
5
6
7
8
9
10
11
function Person(){ }

Person.prototype = {
constructor: Person,
name: "jack",
age: 29,
job: "teacher",
sayName: function(){
alert(this.name);
}
};

原型的动态性

在调用某个属性时, JS 引擎会首先在对象实例中搜索该属性,在没有找到的情况下会继续搜索原型,即使对象实例是在为原型修改属性之前创建也不会有问题。

1
2
3
4
5
6
7
8
function Person(){ }
var friend = new Person();

Person.prototype.sayHi = function (){
alert("hi");
};

friend.sayHi() //没有问题

但是若是重写整个原型对象,情况便不一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(){ }
var friend = new Person();

Person.prototype = {
name: "jack",
age: 29,
job: "teacher",
sayName: function(){
alert(this.name);
}
};

friend.sayName();
// error

因为重写原型对象,切断了现有原型与之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。

原型模式的问题

原型模式省略了为构造函数传递初始化参数这环节,这会使得所有实例在默认情况下获得相同的属性值;而不止于此,其最大的问题在于其共享的本质所导致的,特别是对于包含引用类型值的属性,当我们修改某一个实例对象一个特定属性时必将影响原型对象的,从而使得所有实例属性发生改变。

0x06 组合模式

创建自定义对象最常见的方式就是组合使用构造函数模式和原型模式。 构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person (name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["jack","mac"];
}

Person.prototype = {
constructior: Person,
sayName: function(){
alert(this.name);
}
}

var person1 = new Person("Nich", 20, "Enginner");
var person2 = new Person("Greg", 19, "Student");

person1.friends.push("van");
console.log(person1.friends); //["jack", "mac", "van"]
console.log(person2.friends); //["jack", "mac"]

如上,实例属性都是在构造函数中定义的,而由所有实例共享的属性 constructor 和方法 sayName() 则是在原型定义的。

当修改了 person1.friends, 并不会影响到 person2.friens, 因为它们分别引用了不同的数组。

0x07 动态原型模式

进一步,我们可以将所有信息都封装在构造函数之中,并通过构造函数初始化原型(当然,在必要的条件下)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name, age, job){
//属性
this.name = name;
this.age =age;
this.job = job;

//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function (){
alert(this.name);
}
}
}

var friend = new Person("jack",18,"The walking Dead");
friend.sayName();

注意构造函数中,只有在 sayName() 不存在的情况下,才会将其添加到原型中。

0x08 寄生构造模式

寄生(parasitic)构造模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Person (name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function (){
alert(this.name);
};
retuen o;
}

var friend = new Person("jack", 28, "Enginner");

如上,除了使用 new 操作符并把使用包装的函数叫做构造函数之外,这个模式跟工厂模式一模一样。构造函数在不返回值的情况下,默认会返回新对象实例。通过在构造函数中的 return 语句可以重写调用构造函数时返回的值。

寄生构造模式可以在特殊情况下为对象创建构造函数。

值得注意的是,寄生模式下返回的对象与构造函数或者与构造函数的原型属性之间没有任何关系。构造函数返回的对象与构造函数在外边创建的对象没有什么不同。

0x09 稳妥构造函数模式

所谓稳妥对象(durable object)指的是没有公共属性,而且其方法也不引用 this 的对象。此外稳妥构造模式也不会使用 new 操作符调用构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
function Person (name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function (){
alert(this.name);
};
return o;
}
var friend = Person("jack",28,"Nojob");
friend.sayName();

如此,变量 friend 中保存的是一个稳妥对象,而除了调用 sayName() 方法以外,没有别的方法可以访问其数据成员。

稳妥构造函数模式非常适合某些安全执行环境。

与寄生构造函数一样,稳妥构造函数创建的对象与构造函数之间没有什么关系。

CSS3新增属性

0x00 boxshadow [ CSS3 ]

box-shadow 有以下属性值:

1
2
3
4
5
6
h-shadow : 必需。水平阴影位置。值为正投影在对象右边,值为负,投影在对象左边。
v-shadow : 必需。垂直阴影位置。值为正投影在对象低部。值为负,投影在对象顶部。
color : 阴影颜色。
blur : 可选。模糊距离
radial: 可选。扩展半径。
inset : 可选。将外部阴影(outset)改为内部阴影。

设置四边不同的阴影

1
2
3
4
box-shadow:-10px 0px 5px yellow,
10px 0px 5px red,
0px -10px 5px green,
0px 10px 5px blue;

多重阴影

1
box-shadow:10px 0px 5px yellow,20px 0px 20px green;

text-shadow [ CSS3]

text-shadwoboxs-hadow 基本相同,只是 text-shadwo 不可以定义内阴影。

0x01 box-reflect

这是一个实验中的属性,并且实现情况并不理想,目前只有 Chrome 和 safari 支持。

语法样式:

1
-webkit-box-reflect: below 10px -webkit-linear-gradient(rgba(0,0,0,0.2) 33%,rgba(0,0,0,0.6) 63%,rgba(0,0,0,0.8) 99%);

支持的参数值:Direction,Offset,Mask Value。
分别代表倒影的方向: above,below,left,right
距离原图的距离: 像素
倒影的渐变样式:

0x02 Animation

animation 的子属性有:

animation-name:使用 @keyframes 描述的关键帧名称

animation-duration:动画执行的时间

animation-timing-function:动画速度变化函数

其值有:linear,ease,ease-in,ease-out,ease-in-out
animation-delay:动画延时执行的时间

animation-iteration-count:设置动画重复的次数,infinite 无限次重复动画

animation-direction:动画运行完成以后是反向运行还是重新回到开始位置重复运行。取值有 normalalternate.当设置为 alternate时,表示轮流播放,并且动画会在奇数次数正常播放,在偶数次数反向播放。

animation-fill-mode:指定动画执行前后的元素样式。其常用值有 forwards(当动画执行完成以后,保持最后一个属性值,在最后一个关键帧中定义) 和 backwards(在动画显示前,应用开始属性值,在第一个关键帧中定义)

animation-play-state:控制动画的播发状态,可取值:paused(暂停),running(动画播放).

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
<style type="text/css">
body{
position: relative;
}


#box{
width: 100px;
height: 40px;
background: linear-gradient(red,deeppink,green);
position: absolute;
left: 0;
top:100px;
animation: turnBack 6s linear 1s infinite;
}

@-webkit-keyframes turnBack {
from{
left: 0px;
transform: rotateY(0deg)
} 50% {
left: 600px;
transform: rotateY(0deg)
} 51%{
transform: rotateY(180deg);
}to{
left: 0px;
transform: rotateY(180deg);
}
}


</style>
</head>
<body>
<div id="box">
I'am box,sir!
</div>
</body>

0x03 Transition

Transition 属性用于过渡效果的 CSS 属性。

常见用法:

1
trasition: peroperty duration timing-function delay

函数防抖和函数节流

debounce vs throttle

防抖(Debounce)节流(throttle) 两者都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似而又不同。

“节流”是限制一个一直在执行的函数的最大执行次数。比如让一个本来要一直执行的函数以最多100毫秒执行一次的频率来执行。节流保证你的函数在一定频率下一直执行。

“去抖”则是等一段时间之后再去调用函数(并且函数此时没有被调用)。“去抖”可以让我们把一个连续的的函数调用”打包”成一个。函数去抖是一种空闲控制。

requestAnimationFrame: 可以看做是throttle的替代品。当你的函数有很多的动画渲染或者有很多的元素操作时,你想保证动画的流畅性,就需要用到这个。注意:IE9不支持rAF.

throttle 方法在内部就是调用了带有 maxWait 参数的 _.debounce 来实现的,

两者的区别 : 虽然在等待时间内函数都不会再执行,但 throttle 在第一次触发后开始计算等待时间,debounce 在最后一次触发之后才计算等待时间(最后一次在等待时间范围内)。

当然,这只是基本思想,之实际的应用中,我们还需要考虑一些其它的情况。

函数防抖 debounce

函数防抖其实就是函数在跪规定的时间间隔内被多次调用的话,那么函数不会被执行,直到调用的周期大于规定的时间,那么才开始执行函数,并且只执行一次。

函数防抖的基本模式可以简化如下:

JavaScript高级程序设计中 throttle 其实是函数防抖的实现

1
2
3
4
5
6
function debounce(method, context){
clearTimeout(method.tId)
method.tId = setTimeout(function(){
method.call(context)
}, 500)
}

在 setTimeout() 中用到的函数其执行环境总是 window

debounce 方法调用实例:

1
2
3
4
5
6
7
8
function resizeDiv(){
var div = document.querySelector("#block")
div.style.height = div.offsetWidth + "px"
}

window.onresize = function(){
debounce(resizeDiv)
}

如上,如果我们在 500ms 内连续改变窗口的大小,那么 resizeDiv 只会被执行一次,然后该函数永远不会被执行,知道最后停下来的时间超过 500ms

当然这是个最基本的版本实现,它是存在很多问题的,先不说 fn.timerId 这种形式就不够爽,如果我们的 fn 需要就接受参数呢?或者fn是有返回值的?而且,就目前这个版本实现,函数永远是被延迟执行的,如果需要在函数函数被调用时立即执行呢?

那么,下面直接来看一个 underscoredebounce 的实现:

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
/*
*空闲控制,返回函数连续调用时,空闲时间必须大于或者等于 wait,fn 才会被执行
*
* */

function debounce(fn,wait,immediate) {
let timer;
let result;
// 用于返回 fn 函数的执行结果

let debounced = function(){
let context = this;
/*
* 如果,需要在 fn 使用调用该函数的对象的话,会发现 fn 中的 this 是指向 window 的
* 所以,需要找到 this 对象,并将该对象传递到 fn 函数中
*/
let args = arguments;
/*
* 相应的需要将 args 传递到 fn 中,比如使用 dom.onmousemove = debounce(fn,wait) 时
* arguments 代表的就是 event 对象
* */
let immediate = immediate || true;
// 默认情况下,立即执行
if(timer) clearTimeout(timer);
if(immediate) {
// 如果传入 immediate 参数,表示立即执行
let callNow = !timer;
// 如果已经被执行过,则不会立即执行
if (callNow) result = fn.apply(context,args);
timer = setTimeout(function () {
timer = null;
// 使用 timer = null 去判断距离第一次执行是否已经过了 wait 时间
// 注意 timer = null,与 clearTimeout(timer) 的区别
},wait)
}else {
timer = setTimeout(function () {
// 如果没有立即执行,那么由于 setTimeout 的原因 result 将永远是 undefined
// 所以,我们只在 immediate 的时候返回 fn 函数执行的结果
fn.apply(context,args);
},wait)
}
return result;
}

debounced.canel = function () {
cleatTimeout(timer);
timer = null;
}
return debounced;
}

调用实例:

1
2
3
var lazyLayout = debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
// lazyLayout.cancle(); // 取消函数防抖

soure

函数节流 throttle

函数节流的实现方式有两种,时间戳和定时器。我们先看看时间戳。

1
2
3
4
5
6
7
8
9
10
11
12
function throttle(fn,wait) {
let last=0;
return function () {
let now = +new Date();
let context = this;
let args = arguments;
if (last + wait < now) {
fn.apply(context,args);
last = now;
}
}
}

再看使用定时器实现的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
function throttle(fn,wait) {
let timeout ;
return function () {
let context = this;
let args = arguments;
if(!timeout) {
timeout = setTimeout(function () {
timeout = null;
fn.apply(context,args);
},wait)
}
}
}

两者一比较,我们发现使用时间戳的版本,fn 会在被调用的时刻被立即执行,而使用定时器的版本则是在等待 wait 后才会执行。

那么,如果我们将两者结合在一起呢,就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!

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
function throttle(fn, await){
let timeout = null;
let result = null;
let lastRun = 0;
let throttled = function(){
let context = this;
let args = arguments;
let now = + new Date() || Date.now();
const elapsed = now -lastRun - await;

const runCallback = function(){
timeout = null;
lastRun = now;
fn.apply(context,args);
}

if(elapsed >= 0){
if(timeout){
// 判断定时器是否存在,因为定时器的不准确性,很可能存在等待时间到期后,定时器并没执行的情况。
clearTimeout(timeout);
timeout = null;
}
lastRun = now;
fn.apply(context,args);

}else if(!timeout){
// 保证事件停止触发以后会再执行一次
timeout = setTimeout(runCallback,await)
}

}

throttled.cancle = function(){
// 取消
clearTimeout(timeout);
timeout = null;
lastRun = 0;
}
return throttled;
}