算法
从排序数组中删除重复项
var removeDuplicates = function(nums) {
var i = 1;
while(i < nums.length){
if(nums[i-1] === nums[i]){
nums.splice(i, 1);
}else{
i++;
}
}
return nums.length
};
买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润
var maxProfit = function(prices) {
var total = 0;
var min = prices[0];
for(var i=1; i<prices.length; i++){
if(prices[i] < prices[i-1]){
total += prices[i-1] - min;
min = prices[i];
}
if(i === prices.length - 1){
total += prices[i] - min;
}
}
return total
};
数组是否存在重复元素
var containsDuplicate = function (nums) {
const set = new Set(nums)
return set.size != nums.length
};
只出现一次的数字
var singleNumber = function(nums) {
let result = 0;
nums.forEach(el => (result ^= el));
return result;
};
有效数独 判断一个 9x9 的数独是否有效。
var isValidSudoku = function(board) {
var pieceMap = {};
columnsMap = {}
var res = board.some((arr, index1) => {
var numMap = {};
return arr.some((item, index2) => {
pieceKey = Math.floor(index1/3) + '' + Math.floor(index2/3);
if(!pieceMap[pieceKey]){
pieceMap[pieceKey] = {}
}
if(item === '.'){
return false
}
if(numMap[item]){
return true;
}else{
numMap[item] = true;
}
if(pieceMap[pieceKey][item]){
return true;
}else{
pieceMap[pieceKey][item] = true;
}
if(!columnsMap[index2]){
columnsMap[index2] = {};
}
if(columnsMap[index2][item]){
return true
}else{
columnsMap[index2][item] = true;
}
})
});
if(res){
return false
}
return true
};
n*n 数组矩阵旋转
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
]
// 转换成
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
const rotate = (matrix) => {
matrix.reverse()
for (let i = 0; i < matrix.length; i++) {
for (let j = i + 1; j < matrix[0].length; j++) {
let tmp = matrix[i][j]
matrix[i][j] = matrix[j][i]
matrix[j][i] = tmp
}
}
};
字符串反转
s = "hello" => "olleh"
/*
* 思路1:反向遍历字符串
*/
var s1 = "";
for (var i = s.length-1; i >= 0; i--) {
s1 += s[i];
}
return s1;
/*
* 思路2:1.建立两个指针,分别指向首尾,遍历数组,直至指针相遇;
2.将两个指针值交换
超出时间
*/
// var s1 = Array.from(s);
// var left = 0;
// var right = s.length-1;
// while (left < right) {
// temp = s1[left];
// s1[left] = s1[right];
// s1[right] = temp;
// }
// return s1.join("");
颠倒整数
给定一个 32 位有符号整数,将整数中的数字进行反转。-123 => -321 120 => 21
var reverse = function(x) {
var minn = -(1 << 30) * 2;
var maxn = (1 << 30) * 2 - 1;
var tag = x < 0 ? -1 : 1;
var s = x * tag + '';
var s1 = ''
for (var i = s.length; i--;) {
s1 += s[i];
}
var res = (s1 - 0) * tag;
if (res < minn || res > maxn)
return 0;
else
return res;
};
字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1
var firstUniqChar = function(s) {
var str = s.toLowerCase();
for(var i=0;i<str.length;i++){
var ch = str.charAt(i);
//如果后面没有该字符,且前面也没有出现过
if(str.indexOf(ch) == i && str.lastIndexOf(ch) == i){
return i;
}
}
return -1;
};
有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词
var isAnagram = function(s, t) {
var codeMap = {};
if(s.length !== t.length) return false;
for(var i=0; i<s.length; i++){
var code = s.charCodeAt(i)
codeMap[code] = codeMap[code] ? ++codeMap[code] : 1;
}
for(var i=0; i<t.length; i++){
var code = t.charCodeAt(i)
if(!codeMap[code]) return false;
codeMap[code]--;
}
for(var i in codeMap){
if(codeMap[code] !== 0) return false;
}
return true
};
动画
css 动画
早期动画是通过 setInterval 或者 setTimeout 来修改对应的 css 属相来做动画,这样做的动画偶尔会出现跳帧,动画复杂了之后也会卡顿,主要是性能的瓶颈。
后来有了 requestAnimate 方法,允许在每一帧的空闲时间里调用,还是用 js 去调用 CSSOM 的 API 来做动画。
css3 添加了两个属性:transition 和 animation 来控制动画,我们经常说这两个属性会启动 GPU 加速来调用性能更高的动画。
GPU 加速
以 translate 为例,使用 translate3D 其实是将当前 DOM 层级提高到最上层,使得这个 DOM 的重排都不会影响其他 DOM 的布局,减少重排影响提高重绘
不关注重排之后,只关注重绘。此时就可以用 GPU 单纯来绘制页面,所以加速的并不是说 GPU 本身。而是避免了重排了之后整体绘制性能增强了。
transition
过渡动画:我们把动画如果分成简单的两个状态,起始状态和终止状态,从起始状态平滑的变成终止状态的过程就是动画。transition 就是完成这个平滑过渡的动画
// 示例
transition: property duration timing-function delay;
classA {
width: 100px;
transition: width 2s;
}
classB {
width: 200px;
}
上面这个简单的动画就是两个状态,使用过渡动画将两个状态进行平滑过渡。可以说 transition 是面向结果的,就是两个状态,不能控制动画过程中的变化。
animation
那如果我们期望控制动画过程怎么做呢。使用 animation,animation 会将动画分成 0-100%(或者 from to, from = 0,to = 100%) 这么多帧。
@keyframes
要使用动画之前,首先需要定义动画。将整个动画分成多个动画帧。然后对每个动画帧的状态进行定义。
@keyframes rainbow {
0% { background: #c00 }
50% { background: orange }
100% { background: yellowgreen }
}
@keyframes rainbow {
from { background: #c00 }
50% { background: orange }
to { background: yellowgreen }
}
上面是定义了一个动画 rainbow,两种定义的方法效果是一样的。
animation
animation: name duration timing-function delay iteration-count direction;
animation: rainbow 1s linear 3s 3 normal;
这是使用动画的方式。动画会根据定义的 keyframes 定义的动画那一帧的状态进行设定。
js 动画
首先问自己一个问题有了 css 动画为什么还需要用 js 的动画?
两者其实会有自己的适用场景。
css 动画:适用于两个简单状态的平滑过度,或者是几个关键帧的处理。第一个是用 transition。第二个是用 animation。而且 css 做的动画偏自动化一些。中途如果需要根据状态来判断就不适用了
js 动画:适用于复杂场景的动画,如果需要交互介入就需要 js 的动画了。更高级一些的是现在的 3D 动画。
setTimeout、setInterval
将动画分成诊之后,其实就是每隔多少秒调用一次,上述两个方法在 js 中就是每隔多久调用一次。早期使用这两个方法每隔多少时间进行一次状态的改变。调用 DOM 或者调用 CSSOM,改变当前元素的状态。高度、宽度的改变就是动画
但是如果单个 setInterval 计算太多。就会出现有些 setInterval 丢帧。因为上一个 setInterval 还没有执行,后面的就已经执行过了,造成某些过程跳过,就是跳帧了。
用 setTimeout 模拟 setInterval 这样会防止丢帧,因为下一次 setTimeout 一定要等上一次执行完了才会执行。但是 setTimeout 中的计算太过集中的话就会出现动画卡顿,因为下一帧动画迟迟没有来。
requestAnimationFrame
上述的问题都是因为 js 的执行是分片的。会把执行放在每一个运行单元里,因为 js 是自动 GC,并且页面会有每隔一个 FPS 进行一次屏幕重绘,所以所有的状态都会在那个时刻绘上去。所以 setTimeout 和 setInterval 的问题其实是出在或快或慢于每帧的绘制
后来就有了 requestAnimationFrame API,由于上述问题,所以需要在每次执行动画刷新帧之前执行一帧的状态。如果执行超过当前帧的执行时间,浏览器会停止计算执行,去执行重绘和重排页面。以保证用户看到的页面不会被卡顿。
所以 requestAnimationFrame 用来做 js 的动画是最适合的。但是每次回调里的内容不宜太多涉及到计算。如果有计算最好以异步的方式去执行,而 API 里只是去获取当前应该展示的状态。
GreenSock 的 TweenMax
这是一个动画库,用户自动处理 js 动画的库。用于指定一个 dom 在一段时间点里,将状态改成另一个的状态,实际上就是将两个状态取出来,用时间除一下次时间段里变化的状态,然后每隔一段时间叠加。
TweenMax.to(this.$refs.mask, 0.3, {
opacity: 1
});
还可以将动画分解成一个时间段,将每个状态分成百分比,然后设置当前的进度
var tl = new TweenMax.TimelineLite();
var aaa = { x: 100 }
tl.to(aaa, 1, {
x: 300,
// autoAlpha: 0
})
.to($box, 1, {
x: '+=200px',
// autoAlpha: 1
});
tl.pause();
tl.progress(20%);
gsap 里还有很多动画值得探索,常规的动画都能找到
主轴动画
在这里我自创了一个概念,主轴动画,在做动画时。先找到当前动画中时间线最长的动画,将这个动画的过程作为主轴。将这个动画的时间分成 1-100%,甚至粒度可以更细。然后其他的动画的进度也做成 1-100% 的进度,称为附属动画。
主轴动画随着某些条件来动。比如时间、滚动条。随着时间的变动来设置百分比。附属动画监听主轴动画的百分比变化来设置自己的动画时机。
这个动画就变得可控。更加大型的动画,也可以再分模块,一个主轴附属几个小的主轴,然后更小动画再随着小主轴再动。
3D 动画
canvas.getContext(contextType, contextAttributes);
// canvas 获取绘图上下文
var context = canvas.getContext('2d');
// WebGL 获取绘图上下文
var gl = canvas.getContext('webgl') // 或 experimental-webgl
Canvas 是浏览器封装好的一个绘图环境,在实际进行绘图操作时,浏览器仍然需要调用 OpenGL API。而 WebGL API 几乎就是 OpenGL API 未经封装,直接套了一层壳,threejs 是一个 webGL 框架。
webGL 整体就是用来在 canvas 上绘制 3D 效果的 API。目前封装的比较好的是 threejs。
首先一个 3D 动画由这么几部分元素组成。容器、舞台、演员、天空、相机、控制器等
容器
动画需要有个承载的体现,这个我们一般称为容器,在这里我们一般认为 canvas 标签就是容器。这个容器是用来展示动画的载体。整个容器可以认为是一个无边无际的三维坐标系,并且有正有负。
舞台
舞台就是一个动画的场景,如果我们认为动画是一个 3D 的场景,一个小的动画只用一个场景就可以,但是一个复杂的大型的动画是由一个一个场景组合起来的。每个场景又有自己的动画,区分场景是为了将大的动画分成几组来完成,降低复杂度。
一个舞台就是在这个无边无际的坐标系中的其中一块立体空间。
演员
有了舞台就需要演员来完成动画,动画场景中的每一个动画元素都可以称为一个演员,演员也可以分组,一个复杂的演员可以由多个小的动画元素组成,而每一个动画元素也可以称为一个演员,主要是看其相关性,将演员分组也是降低复杂度的一种。
演员基本上就是场景中的动画元素了,一个复杂的场景动画演员有自己的运动轨迹,演员在场景中完成自己的动画轨迹就完成了自己的动画,如果演员的运动轨迹是由其他因素所决定的,那就根据自己的轨迹去完成。
天空
我们可以理解为整个 3D 动画的场景是被放在一个立体的盒子中的,天空就是盒子内部的内表皮,盒子内部的六个面都可以有自己的颜色图片,我们在盒子内部的舞台中,看起来这个就像是动画的天空。
当然外表皮也是可以着色的,但我们一般是把相机放在舞台内部,所有外表皮的意义并不大。
相机
这个是非常重要的,与其说相机不如说是镜头摆放的位置,threejs 中有两种摄像机方式,正交投影和透视投影两种,最好自己有过实际的 demo 来体会一下两种相机的不同。
相机摆放在不同的位置就能看到不同角度位置的动画,在立体坐标系中,将相机让在某一个舞台面前就能看到这个舞台上的动画。主要是看动画想以哪种形式进行展现,然后设置好相机的位置。动画场景的切换也是通过移动相机,将摄像机放到下一个场景的位置。
控制器
一般情况下我们动画只是播放,很少有交互,简单的交互点击,拖动这些是不用设计到控制器的。控制器是控制动画展现的位置的东西,其实就是控制摄像头的摆放,根据不同的交互类型让摄像头按照某种规律进行运动,主要用于游戏中的交互,可以自行写个 demo 试试每种控制器都有什么方式,分为下面几类:
- OrbitControls:旋转控制器
- FlyControls:飞行控制器
- PointerLockControls:指针控制器
- TrackballControls:轨迹球控制器
- TransformControls:变换物体控制器
动画刷新
动画实际上是由一帧一帧的静态图片连贯组合出来的,也就是说我们想要看到动画,需要不断的刷新页面,然后展现当前动画场景的不同状态,不断的变化的状态连贯起来就是动画了。fps 就不说了,动画是需要 requestAnimationFrame 不断执行,每次执行都去 render 当前动画的状态。
实例
现在动画的实际讲解还是很少,很多都要去翻看很多资料才能收获一点点知识,包括我自己也是一知半解,能做出实际的动画 demo,也做过几个线上的大型场景动画,但是自己要去建模设计还是不是很懂的。下面是相关这些讲解的实际例子。
动画实例
介绍几个 3D 动画框架,threejs 就不说了。
pixi.js
这个是一个国外做动画的第三方框架,可以支持 2D 和 webGL 的渲染,非常容易上手,适合做平面的场景动画。
网易出的好几个动画都是用的 pixi 来做的,网易的动画感觉不错,质量很高,我也模仿过几个。主轴动画的柯南和哈利波特。一镜到底的年度动画
Aframe
是 FF 浏览器出的一套 VR 的组件化方案,通过标签的方式 a-scene 原生支持场景动画,用来做 VR 特别方便。
hilo
hilo 是阿里出的一套动画方案,支持过淘宝双十一动画,万花筒的那个一镜到底的项目,按照官网一步一步走也能搭建出一个 flappy brid,为了看自己是否理解,改变了运动轨迹,让 flappy bird 能永久通关