Coder Social home page Coder Social logo

blogs's People

Contributors

hileix avatar

Watchers

 avatar

blogs's Issues

css3 动画(四)animation.css 源码分析

css3 动画(四)animation.css 源码分析

前言

上一篇 css3 动画(三)animation 简介 中只是简单的介绍了一下 animation 的子属性,并没有真正的使用。在本篇中,通过阅读 animate.css这个 css 的动画库,来加深对
css3 的 animation 属性的理解。

animate.css

animate.css 是一个具有非常多的动画效果的 css 动画库。动画效果演示

用法

<h1 class="animated flash">Example</h1>
  • 加上基础类 animated 以及动画类 flash,就会有 “闪烁” 的动画效果。

动画分类

通过查看 演示,可以看到该动画库的动画类型分为 14 类:

  • Attention Seekers
  • Bouncing Entrances
  • Bouncing Exits
  • Fading Entrances
  • Fading Exits
  • Flippers
  • Lightspeed
  • Rotating Entrances
  • Rotating Exits
  • Sliding Entrances
  • Sliding Exits
  • Specials
  • Zooming Entrances
  • Zooming Exits

在 animate.css 的源码目录中,也根据其分类分为了 14 个文件夹:

clipboard.png

_base.css 基础类

首先看 _base.css 中的基础类:

.animated {
  animation-duration: 1s;
  animation-fill-mode: both;
}

.animated.infinite {
  animation-iteration-count: infinite;
}

.animated.delay-1s {
  animation-delay: 1s;
}

.animated.delay-2s {
  animation-delay: 2s;
}

.animated.delay-3s {
  animation-delay: 3s;
}

.animated.delay-4s {
  animation-delay: 4s;
}

.animated.delay-5s {
  animation-delay: 5s;
}

可以看到:
.animate 基础类设置了动画的两个子属性:animation-duration 和 animation-fill-mode。其值分别为 1s 和 both。animation-fill-mode 详解

.animate.infinite 基础类设置了动画的播放次数为无限次

.animated.delay-ns 基础类设置了动画的延时

示例:flash 动画源码

然后,我们来看一个动画例子的源码:flash.css

@keyframes flash {
  from,
  50%,
  to {
    opacity: 1;
  }

  25%,
  75% {
    opacity: 0;
  }
}

.flash {
  animation-name: flash;
}

在 flash.css 中,首先定义了名为 flash 的关键帧的序列:

@keyframes flash {
  from,
  50%,
  to {
    opacity: 1;
  }

  25%,
  75% {
    opacity: 0;
  }
}

在 0%、50%、100% 关键帧中,其样式 opacity 为 0
在 25%、75% 关键帧中,其样式 opacity 为 1

然后,下面有 .flash 类,使用了 flash 作为 animation-name 属性的值,flash 即为上面定义关键帧的名称

所以,通过添加 flash 类,就可以使元素具有 “闪烁” 的动画效果!

总结

通过上面实例的一个 flash 动画源码的阅读,加深了对 css3 animation 属性的使用。

z-index 详解

z-index 详解

z-index 属性用来控制元素在 z 轴上的顺序

1. 适用范围

z-index 仅适用于定位元素。即 postition 值为 relative, absolutefixed 属性

2. 作用

  • 指定当前元素的 堆叠顺序
  • 创建新的 堆叠上下文

2.1 什么是堆叠顺序

  • 堆叠顺序是当前元素位于 z 轴上的值。值越大表示元素越靠近屏幕,反之元素越远离屏幕
  • 在同一个堆叠上下文中, z-index 值越大,越靠近屏幕。除了 z-index 控制着元素的 堆叠顺序,还有其他因素控制着元素的 堆叠顺序,如下:

2.1 什么是堆叠上下文?

层叠上下文

  • 堆叠上下文 是一个在该元素内的堆叠顺序不会影响到其他堆叠上下文堆叠顺序的一个 环境
  • HTML 文档默认的堆叠上下文: html 元素

3. 其他

  • 元素的堆叠顺序不会高于(或低于)父元素的堆叠顺序

DOM 事件流

DOM

  1. 什么是 DOM:https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model/Introduction

一、DOM 级别与 DOM 事件

  1. DOM 级别:DOM 0 级、DOM 1 级、DOM 2 级、DOM 3 级,4 个DOM 级别
  2. DOM 事件:DOM 0 级事件、DOM 2 级事件、DOM 3 级事件

因为 DOM 1 级中,没有定义事件相关的内容,所以不存在 DOM 1 级事件处理

  1. 各级 DOM 对事件的处理
  • DOM 0 级事件处理:
var div = document.getElementById('div');
div.onclick = function() {
  console.log('div clicked');
}
  • DOM 2 级事件处理:
var div = document.getElementById('#div');
div.addEventListener('click', handleDivClick, false);
function handleDivClick() {
  console.log('div clicked');  
}
  • DOM 3 级事件处理:
    在 DOM 2 级事件的基础上添加了更多的事件,也允许使用自定义事件

二、事件流

  1. 什么是事件流?
  • 事件流,就是事件传播的方向。因为每个 dom 都可以绑定事件,所以事件的触发,需要一个顺序
  1. 在 DOM 2 级事件中,规定了事件流会处于三个阶段:
  • 捕获阶段:window -> document -> html -> body -> div
  • 处于目标阶段:div
  • 冒泡阶段:div -> body -> html -> documen -> window

通过 Element.addEventListener(type, callback, useCapture) 来监听一个元素的事件,通过改变第三个参数,即可改变事件流:

当 useCapture 为 true 时,表示事件会在捕获阶段执行事件的回调函数:window -> document -> html -> body -> div
否则事件会在冒泡阶段执行事件的回调函数:div -> body -> html -> document -> window

三、事件对象

  1. 事件对象是事件回调函数的 event 对象

  2. event 对象有一些比较重要的方法:

  • event.stopPropagation():阻止事件冒泡,即阻止事件继续向上传播
  • event.preventDefault():阻止事件的默认行为

四、事件委托

  1. 事件委托就是利用事件冒泡,只指定一个事件处理程序,来管理某一类型的所有事件
  2. 原理:事件冒泡
  3. 优点:
  • 可以实现动态绑定事件
  • 可以节省内存
  1. 缺点:
  • 并不是所有的事件都 支持 事件委托(focus、blur 等不支持事件冒泡的事件)
  • 并不是所有的事件都 适合 使用事件委托(mousemove、mouseout 需要大量的计算,不适合使用)

前端面试总结-2018.5.25

个人前端面试总结

前言

在工作了两年之后,就一直想着跳槽,去一家大公司。在 5 月 25 号(2019 年)时,我向老板提了离职,并且此时我开始在网上投简历,开始面试。

我的打算是,一开始先去面面看,积累积累面试的经验,等做好了充分的准备之后,再投大公司的简历。

第一家公司

这家公司是一家不太大的公司。

首先是做了两道面试题,在半个小时之内做出来。我做出了一道。然后是面试官和 hr 对我进行面试,问了一些基础的问题:

  1. 介绍一些我工作时所做的项目
  2. 什么是事件冒泡
  3. vue 和 jQuery 相比,有什么不同?
  4. 介绍一下 cookie、session

第二家公司(晓信)

第二家公司是 “晓信”,是一家挺大的公司。

面试题

  1. 页面导入样式时,使用 link 和 @import 有什么区别?
  2. cookie、sessionStorage 和 localStorage 之间的区别
  3. title 与 h1 的区别,b 与 strong 的区别,i 与 em 的区别
  4. 你有用过哪些前端优化的方法
  5. vue 为什么要避免 v-if 和 v-for 用在同一个元素上

面试官面试的提问

  1. 介绍一下自己(我是第一次见到面试官问这样的问题,当时有点懵,没有准备,就随便介绍了一下自己)
  2. 你平时用过哪些 es6 的特性
  3. 如何实现不用一个临时变量,来交换数组中两个元素的位置
  4. 介绍一下 Promise
  5. 介绍一下 async 函数

在面试时,因为没有准备好,回答的挺差的。未通过面试。

第三家公司(unity)

面试题

面试官提问

  1. 介绍一下自己

这次,我有了充分的准备。主要是介绍了自己毕业自哪里;所学专业是什么;自己所掌握的技术有哪些;在上家公司主要是做什么工作的;自己对自己的评价以及自己的优势;

  1. 介绍一下简历中的项目
  2. 介绍一下 react 的生命周期
  3. 介绍一下 http、https,以及他们有什么不同
  4. 介绍一下 flex
  5. 介绍一下 Promise
  6. 介绍一下 async 函数
  7. 手写代码,求出一个字符串中最长的回文字符串

通过了面试。

第四家公司(喜马拉雅)

面试题

第一轮技术面试

  1. 介绍一下自己
  2. 介绍一下简历中所做的项目
  3. 介绍一下 react 的高阶组件
  4. 介绍一下 react virtual dom
  5. 介绍一下 react virtual dom 的 diff 算法
  6. 什么是单页应用
  7. 介绍一下 seo
  8. 介绍一下单页应用
  9. 介绍一下 express
  10. 介绍一下浏览器的缓存数据的方式(cookit/sessionStorage/localStorage/indexedDB)
  11. 什么是原型,原型链
  12. js 中,如何实现继承,并手写代码实现 js 的继承
  13. 如何实现响应式布局

第二轮面试

  1. 介绍一下自己
  2. 介绍一下你简历中你觉得最值得介绍的一个项目
  3. 你未来 3 年的规划是什么
  4. 然后面试官介绍了一下目前的业务线

第三轮面试

  1. 介绍一下自己
  2. 介绍一下 react 16.3 版本之前的生命周期
  3. 介绍一下 react 16.3 版本之后的生命周期,以及为什么要这样做
  4. 介绍一下 react hooks
  5. 介绍一下 react fiber 架构
  6. 介绍一下 Promise 以及与 Promise 相关的所有东西(callback/async 函数)
  7. 面试官出了一道 async 的题目,需要手写代码

通过了面试。

总结

  1. 自我介绍这一块要准备好,因为这是给面试官的第一印象。
  2. 面试前要多看看面试题,做好充分的准备。
  3. 简历要如实填写。因为面试官基本上都是按照简历上你写的掌握的技能来提问的。

css3 动画(一) transition

css3 动画(一) transition

前言

最近在研究 css3 的动画属性 transition 和 animation,发现自己之前对这两个 css3 的动画属性并没有太多深入的理解。本篇将介绍 css3 的 transition 以及自己的一些理解。

transition

首先,transition 意为 “过渡,转场”,即从一种状态变为另外一种状态的中间过程。css3 的 transition 属性,就是用来配置这个中间状态的。

css3 中的 transition 属性是 css3 过渡的四个属性的简写形式,其四个 css3 的属性分别为:

例子:

<div>transition</div>
div {
  background: #fff;
  color: #000;
  transition: all 1s linear 0.5s;
}
div:hover {
  background: #000;
  color: #fff;
}

效果如下:
https://codepen.io/reai3041/pen/Vdoqvm?editors=1100

在代码中,可以看到,我们需要预先使用 transition 属性,配置选择器样式的变化过程。这个过程包含了四个属性需要有过渡效果的属性过渡时间过渡速度变化函数以及延迟多久开始过渡

然后,配置完 transition 属性之后,在用户产生某个行为时(如鼠标 hover 到该元素上时),重新设置需要有过渡效果的属性的新值

在上面的例子中:

  • div 没 hover 时的状态(开始状态)为:背景颜色为白色,字体颜色为黑色
  • div 被 hover 时的状态(结束状态)为:背景颜色为黑色,字体颜色为白色
    倘若不使用 css3 的 transition 过渡属性的话,则当 div 被 hover 时,div 的背景和字体样式会瞬间没 hover 时的状态 变为 被 hover 时的状态,没有过渡的效果。

上面的例子中,使用了 transition 属性:

  • 指定了 all,即 div 的所有可以变化的样式变化时都有过渡效果
  • 指定了过渡时长为 1s
  • 指定了过渡函数为 linear(线性),即变化速度为匀速
  • 指定了延时为 0.5s,即 hover 0.5s 之后,才开始过渡

注意

只有当过渡状态过程中,transition 属性存在(transition 被选择器应用时)时,才会有过渡的效果,否则没有过渡效果。

什么意思呢?

我们可以看到上面的例子中:

  • 鼠标移入 div 时,是有过渡效果的
  • 鼠标移开 div 时,也是有过渡效果的
    因为此时 transition 属性是设置在 div 选择器上的,不管移入和移出 div,transition 属性都在过渡的过程中被 div 应用上了。

我们改一下代码(将 transition 属性应用在 div 被 hover 时的选择器上)

div {
  background: #fff;
  color: #000;
}
div:hover {
  background: #000;
  color: #fff;
  transition: all 1s linear 0.5s;
}

效果如下:
https://codepen.io/reai3041/pen/zagybK

此时可以看到:

  • 鼠标移入 div 时,有过渡效果
  • 鼠标移出 div 时,没有过渡效果
    移入时,transition 在 div:hover 选择器上将 transition 属性应用在了 div 上,即在过渡的过程中,div 是有 transition 属性应用的

移出时,div 上没有了 transition 属性,此时便没有了过渡效果,而是瞬间效果

总结

1. 其实 transition 很简单,就四个属性:

  • 有过渡效果的属性
  • 过渡时长
  • 过渡函数(下一篇讲)
  • 过渡时延

2. 注意只有当选择器在过渡的过程中,被应用了 transition 属性,才会有过渡效果,否则是没有过渡效果的,只有瞬间效果

下一篇:css3 动画(二)贝塞尔曲线以及利用 transition 和 贝塞尔曲线函数写出一个加入购物车的动态效果(平抛运动效果)

js 的继承

js 的继承

1. 原型链实现继承

1.1 代码实现

// 父类型
function SuperType() {
  this.property = true;
}
SuperType.prototype.getSuperValue = function() {
  return this.property;
};

// 子类型
function SubType() {
  this.subproperty = false;
}

// 子类型继承 SuperType
SubType.prototype = new SuperType();

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

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

原型链实现继承的关键代码:SubType.prototype = new SuperType;

  • 本质上是重写了子类型的原型对象,其值为父类型的实力对象。这样的话,就可以通过原型链查找到父类型的属性和方法

1.2 存在的问题

  • 第一个问题:若父类型中存在包含引用类型的实例属性,则该父类型的引用类型的实例属性会被所有的子类型共享
// 父类型
function SuperType() {
  this.colors = ['red', 'blue', 'green'];
}

// 子类型
function SubType() {}

// 通过原型链实现继承
SubType.prototype = new SuperType(); // { colors: ['red', 'blue', 'green'] }

// 实例 1
var instance1 = new SubType();
// 操作实例 1 的 colors 属性
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']

// 实例 2
var instance2 = new SubType();
console.log(instance2.colors); // ['red', 'blue', 'green', 'black']
  • 第二个问题:在创建子类型实例时,不能向父类型的构造函数中传递参数

2. 借用构造函数

2.1 代码实现

// 父类型
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

// 子类型
function SubType() {
  // 执行父类型构造函数,且修改其运行时的 this 值
  // this 值表示子类型的实例对象
  // 所以当 new SubType 时,此时子类型的实例对象就拥有了 colors 属性
  SuperType.call(this, 'Bob');
}

var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']

var instance2 = new SubType();
console.log(instance2.colors); // ['red', 'blue', 'green']

借用构造函数实现继承关键代码:SuperType.call(this)

  • 解决了 原型链实现继承 中的子类型中的引用类型的实例属性会被共享以及不能给父类型构造函数传参的问题

2.2 存在的问题

  • 因为所有的方法都要在构造函数里定义,所以无法共享方法

3. 组合继承

组合继承就是将 原型链实现继承借用构造函数实现继承 结合起来

3.1 代码实现

// 父类型
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};

// 子类型
function SubType(name, age) {
  // 继承属性
  SuperType.call(this, name);
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

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

  • 使用 借用构造函数 继承属性
  • 使用 原型链 继承方法

3.2 存在的问题

  • 会调用两次父类型的构造函数

4. 原型式继承

4.1 代码实现

function object(o) {
  // 定义 F 构造函数
  function F() {}
  // 将需要继承的对象赋值给 F 的原型
  F.prototype = o;
  // 返回实例对象,通过该实例对象可以访问到 F 的原型对象,所以可以访问到 o 对象的属性
  return new F();
}

// 或者通过 Object.create() 方法实现

5. 寄生式继承

5.1 代码实现

function createAnother(original) {
  // 使用原型式继承的 object 方法(如上)
  var clone = object(original);
  // 添加方法,对 clone 对象增强
  clone.sayHi = function() {
    console.log('hi);
  }
  return clone;
}

6. 寄生组合式继承

6.1 代码实现

function inheritPrototype(subType, superType) {
  // 复制一份父类型的原型对象
  var prototype = Object(superType.prototype);
  // 然后赋值给子类型的原型
  // 这样子类型的实例就可以通过原型访问到父类型原型上的方法和属性了
  prototype.constructor = subType;
  subType.prototype = prototype;
}

// 父类型
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
  console.log(this.name);
};

// 子类型
function SubType(name, age) {
  // 继承属性
  SuperType.call(this, name);
  this.age = age;
}
// 继承方法
inheritPrototype(SubType, SuperType);

寄生组合式继承结合了寄生和组合的优点,是引用类型最理想的继承范式

  • 复制父类型的原型对象,赋值给子类型

浏览器 fps、requestAnimationFrame() 以及 requestIdleCallback()

浏览器 fps、requestAnimationFrame() 以及 requestIdleCallback()

1. 前言

最近在研究 react 源码(v16.8)的过程中,发现 react 使用了 requestAnimationFramerequestIdleCallback 这两个 api。同时,这两个 api 与 浏览器的 fps 存在着关系。

2. 浏览器的 fps

2.1. fps 是什么?

首先介绍一下一些名词的含义

  • 帧:指显示器显示的每一张画面

fps 全称为 Frames Per Second,即 每一秒的帧数。我们在显示器上看到的各种各样的动画效果,都是由一帧一帧组成的。可以将 fps 理解为动画播放的速度。fps 越大,动画越流畅。

一般浏览器的 fps 为 60。当然,如果你的显示器的刷新率能够达到 144 hz 的话,浏览器的 fps 可以达到 144 fps

那什么是刷新率呢?屏幕的刷新率 是指 屏幕每秒能够显示图像的次数,单位为 hz(赫兹)。

fps 的值受限于屏幕的刷新率,即 fps 的值小于等于屏幕刷新率的值

总结:浏览器的 fps 指浏览器每一秒的帧数。fps 越大,每秒的画面就越多,浏览器的显示就越流畅。浏览器的 fps 一般等于屏幕的刷新率,不会超过屏幕的刷新率。

3. requestAnimationFrame()

先看一下 MDN 上对 requestAnimationFrame() 的定义

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配

  • window.requestAnimationFrame() 会在浏览器重绘前调用传入的回调函数
  • 回调函数的执行次数与浏览器的最大 fps 相等

范例

var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

function step(timestamp) {
  if (!start) start = timestamp;
  var progress = timestamp - start;
  element.style.left = Math.min(progress / 10, 200) + 'px';
  if (progress < 2000) {
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);
  • element.style.left 的值会每 16.7ms(1000 / 60)变化一次,一秒变化 60 次,这样就形成了动画

image

  • 如上图中的 rAf 就表示 requestAnimationFrame()。在 Update Rendering(更新渲染)前执行

requestAnimationFrame() 总结:

  • 特点:requestAnimationFrame() 一定会在 重绘前 去执行传入的回调
  • 优点:当requestAnimationFrame() 运行在 后台标签页 或者隐藏的 <iframe> 里 时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命

4. requestIdleCallback()

MDN 上对 requestIdleCallback() 的定义

window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应

  • 什么叫 浏览器的空闲时间段 呢?来看一下刚刚这张图:

image

在一个最大 fps 为 60 的浏览器中,每一帧的时间长度我们可以得出:1000 / 60 = 16.7ms。当浏览器在 Update Rendering 这个阶段以及之前的阶段所用的时间小于 16.7 ms 时,就会有 idle time(空闲时间)。这个 idle time 就是 浏览器的空闲时间段

  • 再看下图。当浏览器动画达到最流畅的效果时(也就是 60 fps),浏览器给每一帧所分配的时间为 16.7ms。60 帧中,就有 60 个 idle time。这可不是一笔小的时间长度(这段时间不用白不用~)。

  • 所以,在 react 中就会利用这段时间,将对于用户来说,不那么重要的任务放在 idle time 中执行。而对于用户体验来说比较重要的任务(如:动画、click 事件任务等)放在 requestAnimationFrame() 回调中执行。

image

  • 当然,如果一直没有 空闲时间requestIdleCallback() 的回调就会一直得不到调用。我们可以通过设置第二个的 timeout 属性,让 requestIdleCallback()timeout 时间之后,必定执行。

5. 参考

使用 git alias 提高 git 的使用效率

使用 git alias 提高 git 的使用效率

前言

git 作为一个版本控制工具,是我们程序员平时工作中不可缺少的一部分。但有一个问题,我们开发完一个小功能或修改了一个 bug,都需要 add 然后 commit 一下,每次都要敲这么多的字符。作为经常使用 git 的我们来说,这是不能忍受的!

这个时候,可以使用 git alias

定义自己的 git alias

通过命令设置 alias

根据 git 官方文档说明,我们可以通过以下命令定义 git alias:

git config --global alias.a add
git config --global alias.c commit
git config --global alias.o checkout

通过 git 配置文件设置 alias

上面那种用命令定义 alias 的方式,需要敲这么多前置的命令,太麻烦了。这个时候,我们可以通过 git 的配置文件来配置 alias

  1. ~/ 目录下找到 .gitconfig 文件
  2. .gitconfig 文件末尾添加:
[alias]
a = add
c = commit
o = checkout
# ...
  1. 完成!

这样,我们就可以直接使用 git agit cgit o 来代替 git addgit commitgit o 啦!

git alias

之前的都是我们自己配置的一些 git alias,当然有别人给我们配好了的:git alias。里面包含了非常非常非常多的 git alias,具体的 alias 所对应的真正的 git 命令,可以查看该项目的 gitalias.txt 文件。

# 如:
# gitalias.txt 文件中一个单词的 alias
  a = add
  b = branch
  c = commit
  d = diff
  f = fetch
  g = grep
  l = log
  m = merge
  o = checkout
  p = pull
  r = remote
  s = status
  w = whatchanged
安装使用
  1. 首先将该开源项目中的 gitalias.txt 文件下载下来
  2. 然后在刚刚我们编辑的 .gitconfig 文件里面加入:
[include]
path = gitalias.txt
  1. 这样,gitalias.txt 中的所有 alias,都已被引入,就可以直接使用了!
git 命令用 g 命令替代
  1. 打开 ~/.bash_profile 文件
  2. 在文件末尾添加:
alias g=git
  1. 使用 source ~/.bash_profile 命令
  2. 完成

这样,git 也可以使用 g 命令来替代了!

更多 git alias 工具

根据 git alias more ideas 介绍,我们可以使用其他工具来使用 git alias,如:

  • 如果有 node 环境(作为前端开发,必须有!),可以使用 git-alias

总结

完~

package.json 中的 main 和 module 字段

package.json 中的 main 和 module 字段

main(npm 官方指定的程序入口字段)

  • main 指定了该 package 的入口,引入 package 时,就是引入该入口文件:
{
  "name": "foo",
  "main:: "lib/index.js"
}
// 此时导入的 foo package 就是导入 node_modules/foo/lib/index.js 文件
const foo = require('foo');

module 字段(非 npm 官方指定字段)

  • module 字段不是 npm 官方指定的字段,而是在打包工具中一种约定俗成的字段,用来表示 ES2015 module 规范的代码的入口
  • 在 webpack 4 中,可以配置 resolve.mainFields 来指定 package.json 中的哪个 field 表示 package 的入口
其也有默认值,默认值与 target 配置有关:
1. 如果 target 为 webworker, web 或 未指定,则:
module.exports = {
  //...
  resolve: {
    mainFields: ['browser', 'module', 'main']
  }
};
2. target 为其他值(如:node)
module.exports = {
  //...
  resolve: {
    mainFields: ['module', 'main']
  }
};
优先级为在前面的优先级高

两数之和

两数之和

题目链接

  1. 暴力法
    两个 for 循环
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  const ret = [];
  for (let i = 0; i < nums.length - 1; i++) {
    let flag = false;
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] + nums[j] === target) {
        ret.push(i);
        ret.push(j);
        flag = true;
        break;
      }
    }
    if (flag) {
      break;
    }
  }
  return ret;
};
  • 时间复杂度:$O(n^2)$
  • 空间复杂度:$O(1)$
  1. 将数组转换为对象(或 Map),对象的键为数组的值,对象的值为数组的索引
  • 对象:
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  let ret = [];
  const numsObj = {};
  for (let i = 0; i < nums.length; i++) {
    numsObj[nums[i]] = i;
  }
  for (let i = 0; i < nums.length; i++) {
    const value = target - nums[i];
    const index = numsObj[value];
    if (typeof index === 'number' && i !== index) {
      ret = [i, index];
      break;
    }
  }
  return ret;
};
  • Map:
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  let ret = [];
  const numsMap = new Map();
  for (let i = 0; i < nums.length; i++) {
    numsMap.set(nums[i], i);
  }
  for (let i = 0; i < nums.length; i++) {
    const value = target - nums[i];
    const index = numsMap.get(value);
    if (typeof index === 'number' && i !== index) {
      ret = [i, index];
      break;
    }
  }
  return ret;
};
  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$
  1. 再对第二种方法进行优化一下,只遍历一遍
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  let ret = [];
  const numsObj = {};
  for (let i = 0; i < nums.length; i++) {
    const value = target - nums[i];
    const index = numsObj[value];
    if (typeof index === 'number' && i !== index) {
      ret = [i, index];
      break;
    }
    numsObj[nums[i]] = i;
  }
  return ret;
};
  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

用 nodejs 写一个命令行工具

用 nodejs 写一个命令行工具:创建 react 组件的命令行工具

前言

上周,同事抱怨说 react 怎么不能像 angular 那样,使用命令行工具来生成一个组件。对呀,平时工作时,想要创建一个 react 的组件,都是直接 copy 一个组件,然后做一些修改。为什么不能将这个过程交给程序去做呢?当天晚上,我就仿照 angular-cli 的 api,写了一个生成 react 组件的命令行工具 rcli。在这里记录一下实现的过程。

api 设计

0.1.0 版本的 rcli 参照 angular-cli 的设计,有两个功能:

  1. 使用 rcli new PROJECT-NAME 命令,创建一个 react 项目,其中生成项目的脚手架当然 create-react-app
  2. 使用 rcli g component MyComponent 命令,创建一个 MyComponent 组件,这个组件是一个文件夹,在文件夹中包含 index.jsMyComponent.jsMyComponent.css 三个文件

后来发现 rcli g component MyComponent 命令在 � 平时开发过程中是不够用的,因为这个命令只是创建了一个类组件,且继承自 React.Component

在平时开发过程中,我们会用到这三类组件:

  1. 继承自 React.Component 的类组件
  2. 继承自 React.PureComponent 的类组件
  3. 函数组件(无状态组件)

注:将来可以使用 Hooks 来代替之前的类组件

于是就有了 0.2.0 版本的 rcli

0.2.0 版本的 rcli

用法

Usage: rcli [command] [options]

Commands:
  new <appName>
  g <componentName>

`new` command options:
  -n, --use-npm                    Whether to use npm to download dependencies

`g` command options:
  -c, --component <componentName>  The name of the component
  --no-folder                      Whether the component have not it's own folder
  -p, --pure-component             Whether the component is a extend from PureComponent
  -s, --stateless                  Whether the component is a stateless component
使用 create-react-app 来创建一个应用
rcli new PROJECT-NAME
cd PROJECT-NAME
yarn start

或者你可以使用 npm 安装依赖

rcli new PROJECT-NAME --use-npm
cd PROJECT-NAME
npm start
生成纯组件(继承自 PureComponent,以提高性能)
rcli g -c MyNewComponent -p
生成类组件(有状态组件)
rcli g -c MyNewComponent

等于:

rcli g -c ./MyNewComponent
生成函数组件(无状态组件)
rcli g -c MyNewComponent -s
生成组件不在文件夹内(也不包含 css 文件和 index.js 文件)
# �默认生成的组件都会都�包含在文件夹中的,�若�不想生成的组件被文件夹包含,则加上 --no-folder ��选项
rcli g -c MyNewComponent --no-folder

实现过程

1. 创建项目

  • 创建名为 hileix-rcli 的项目
  • 在项目根目录使用 npm init -y 初始化一个 npm package 的基本信息(即生成 package.json 文件)
  • 在项目根创建 index.js 文件,用来写用户输入命令后的主要逻辑代码
  • 编辑 package.json 文件,添加 bin 字段:
{
  "name": "hileix-rcli",
  "version": "0.2.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "rcli": "./index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/hileix/rcli.git"
  },
  "keywords": [],
  "author": "hileix <[email protected]> (https://github.com/hileix)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/hileix/rcli/issues"
  },
  "homepage": "https://github.com/hileix/rcli#readme",
  "dependencies": {
    "chalk": "^2.4.1",
    "commander": "^2.19.0",
    "cross-spawn": "^6.0.5",
    "fs-extra": "^7.0.1"
  }
}
  • 在项目根目录下,使用 npm link 命令,创建软链接指向到本项目的 index.js 文件。这样,就能再开发的时候,直接使用 rcli 命令直接进行测试 ~

2. rcli 会依赖一些包:

  • commander:tj 大神写的一款专门处理命令行的工具。主要用来解析用户输入的命令、选项
  • cross-spawn:nodejs spawn 的跨平台的版本。主要用来创建子进程执行一些命令
  • chalk:给命令行中的文字添加样式。
  • path:nodejs path 模块
  • fs-extra:提供对文件操作的方法

3.实现 rcli new PROJECT-NAME

#!/usr/bin/env node

'use strict';

const program = require('commander');
const log = console.log;

// new command
program
  // 定义 new 命令,且后面跟一个必选的 projectName 参数
  .command('new <projectName>')
  // 对 new 命令的描述
  .description('use create-react-app create a app')
  // 定义使用 new 命令之后可以使用的选项 -n(使用 npm 来安装依赖)
  // 在使用 create-react-app 中,我们可以可以添加 --use-npm 选项,来使用 npm 安装依赖(默认使用 yarn 安装依赖)
  // 所以,我将这个选项添加到了 rcli 中
  .option('-n, --use-npm', 'Whether to use npm to download dependencies')
  // 定义执行 new 命令后调用的回调函数
  // 第一个参数便是在定义 new 命令时的必选参数 projectName
  // cmd 中包含了命令中选项的值,当我们在 new 命令中使用了 --use-npm 选项时,cmd 中的 useNpm 属性就会为 true,否则为 undefined
  .action(function(projectName, cmd) {
    const isUseNpm = cmd.useNpm ? true : false;
    // 创建 react app
    createReactApp(projectName, isUseNpm);
  });

program.parse(process.argv);

/**
 * 使用 create-react-app 创建项目
 * @param {string} projectName 项目名称
 * @param {boolean} isUseNpm 是否使用 npm 安装依赖
 */
function createReactApp(projectName, isUseNpm) {
  let args = ['create-react-app', projectName];
  if (isUseNpm) {
    args.push('--use-npm');
  }
  // 创建子进程,执行 npx create-react-app PROJECT-NAME [--use-npm] 命令
  spawn.sync('npx', args, { stdio: 'inherit' });
}

上面的代码边实现了 rcli new PROJECT-NAME 的功能:

  • #!/usr/bin/env node 表示使用 node 执行本脚本

4.实现 rcli g [options]

#!/usr/bin/env node

'use strict';
const program = require('commander');
const spawn = require('cross-spawn');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs-extra');

const log = console.log;

program
  // 定义 g 命令
  .command('g')
  // 命令 g 的描述
  .description('Generate a component')
  // 定义 -c 选项,接受一个必选参数 componentName:组件名称
  .option('-c, --component-name <componentName>', 'The name of the component')
  // 定义 --no-folder 选项:表示当使用该选项时,组件不会被文件夹包裹
  .option('--no-folder', 'Whether the component have not it is own folder')
  // 定义 -p 选项:表示当使用该选项时,组件为继承自 React.PureComponent 的类组件
  .option(
    '-p, --pure-component',
    'Whether the component is a extend from PureComponent'
  )
  // 定义 -s 选项:表示当使用该选项时,组件为无状态的函数组件
  .option(
    '-s, --stateless',
    'Whether the component is a extend from PureComponent'
  )
  // 定义执行 g 命令后调用的回调函数
  .action(function(cmd) {
    // 当 -c 选项没有传参数进来时,报错、退出
    if (!cmd.componentName) {
      log(chalk.red('error: missing required argument `componentName`'));
      process.exit(1);
    }
    // 创建组件
    createComponent(
      cmd.componentName,
      cmd.folder,
      cmd.stateless,
      cmd.pureComponent
    );
  });

program.parse(process.argv);

/**
 * 创建组件
 * @param {string} componentName 组件名称
 * @param {boolean} hasFolder 是否含有文件夹
 * @param {boolean} isStateless 是否是无状态组件
 * @param {boolean} isPureComponent 是否是纯组件
 */
function createComponent(
  componentName,
  hasFolder,
  isStateless = false,
  isPureComponent = false
) {
  let dirPath = path.join(process.cwd());
  // 组件在文件夹中
  if (hasFolder) {
    dirPath = path.join(dirPath, componentName);

    const result = fs.ensureDirSync(dirPath);
    // 目录已存在
    if (!result) {
      log(chalk.red(`${dirPath} already exists`));
      process.exit(1);
    }
    const indexJs = getIndexJs(componentName);
    const css = '';
    fs.writeFileSync(path.join(dirPath, `index.js`), indexJs);
    fs.writeFileSync(path.join(dirPath, `${componentName}.css`), css);
  }
  let component;
  // 无状态组件
  if (isStateless) {
    component = getStatelessComponent(componentName, hasFolder);
  } else {
    // 有状态组件
    component = getClassComponent(
      componentName,
      isPureComponent ? 'React.PureComponent' : 'React.Component',
      hasFolder
    );
  }

  fs.writeFileSync(path.join(dirPath, `${componentName}.js`), component);
  log(
    chalk.green(`The ${componentName} component was successfully generated!`)
  );
  process.exit(1);
}

/**
 * 获取类组件字符串
 * @param {string} componentName 组件名称
 * @param {string} extendFrom 继承自:'React.Component' | 'React.PureComponent'
 * @param {boolean} hasFolder 组件是否在文件夹中(在文件夹中的话,就会自动加载 css 文件)
 */
function getClassComponent(componentName, extendFrom, hasFolder) {
  return '省略...';
}

/**
 * 获取无状态组件字符串
 * @param {string} componentName 组件名称
 * @param {boolean} hasFolder 组件是否在文件夹中(在文件夹中的话,就会自动加载 css 文件)
 */
function getStatelessComponent(componentName, hasFolder) {
  return '省略...';
}

/**
 * 获取 index.js 文件内容
 * @param {string} componentName 组件名称
 */
function getIndexJs(componentName) {
  return `import ${componentName} from './${componentName}';
export default ${componentName};
`;
}
  • 这样就实现了 rcli g [options] 命令的功能

总结

  • api 设计是很重要的:好的 api 设计能让使用者更加方便地使用,且变动少
  • 当自己想不到该怎么设计 api 时,可以参考别人的 api,看看别人是怎么设计的好用的

React Native 屏幕适配(炒鸡简单的方法)

React Native 屏幕适配(炒鸡简单的方法)

前言

React Native 可以开发 ios 和 android 的 app,在开发过程中,势必会遇上屏幕适配(ios 好几种尺寸的屏幕以及 android 各种尺寸的屏幕)的问题,下面介绍一种几行代码搞定 RN 适配的方法!

屏幕适配的前置知识

  • RN 中的尺寸单位为 dp,而设计稿中的单位为 px

原理

虽然单位不同,但是元素所占屏幕宽度的比例是相同的
利用元素所占屏幕比例不变的特性,来将 px 转为 dp(这样实现屏幕适配的话,在不同尺寸的屏幕下,元素会等比放大或缩小)

公式如下:

设计稿元素宽度(px) / 设计稿总宽度(px) = 元素的宽度(dp) / 屏幕的总宽度(dp)

我们要求的就是 元素的宽度(dp)

可以得出:

元素的宽度(dp) = 设计稿元素宽度(px)* 屏幕的总宽度(dp) / 设计稿总宽度(px)

代码实现

// util.js
import { Dimensions } from 'react-native';

// 设备宽度,单位 dp
const deviceWidthDp = Dimensions.get('window').width;

// 设计稿宽度(这里为640px),单位 px
const uiWidthPx = 640;

// px 转 dp(设计稿中的 px 转 rn 中的 dp)
export const pTd = uiElePx => {
  return (uiElePx * deviceWidthDp) / uiWidthPx;
};

使用

每次给元素设置尺寸样式时,使用 pTd() 函数即可(传入设计稿中元素的实际 px)。

Event Loop 事件循环

Event Loop 事件循环

说到 event loop,不得不提的是: JavaScript 是单线程的。即一次只能做一件事情。Event Loop 就是让 JavaScript 在运行时具有并发的能力。

1. 同步任务与异步任务

// global sync task

function add(a, b) {
  return a + b;
}

// add sync task
const sum = add();

// bar async task
setTimeout(function bar() {
  console.log('bar called!');
}, 1000);

// fetchUsers async task
fetch('https://www.example.com/api/v1/users').then(function fetchUsers(res) {
  console.log({ res });
});
  • 上面的脚本中包含同步任务有 global taskfoo task,包含的异步任务有 bar task
  • 同步:函数被调用时能够立即得到结果
  • 异步:函数被调用时,不能立即得到返回值,而是在将来的某个时刻得到返回值

2. 执行栈

要想理解 Event Loop,就得先了解 JavaScript 中的 执行栈(JavaScript execution context stack)

JavaScript 是单线程的,只有一个主线程。在这个主线程中,有一个栈。

在每个函数 运行时,会生成一个 执行上下文(execution context),这个执行上下文包含了当前函数运行时的参数,局部变量等信息。

开始执行函数 时,执行上下文会被 推到执行栈中函数执行完毕后,这个 执行上下文又会从栈中弹出

例子

function foo() {
  console.log('foo');
}

function bar() {
  console.log('bar');
  foo();
}

bar();
  • 如下图所示

event-loop

  • 入执行栈:

  • 整个脚本开始运行,global 执行上下文 被推入 执行栈

  • bar() 函数开始运行,bar() 函数执行上下文 被推入 执行栈

  • foo() 函数开始运行,foo() 函数执行上下文 被推入 执行栈

  • 出执行栈:

  • foo() 函数执行完毕,弹出

  • bar() 函数执行完毕,弹出

总结:执行栈 相当于一条流水线,所有的 同步任务 运行时,都会被放到这条流水线上去一个一个 按照顺序(函数调用顺序) 执行。

既然 同步任务运行时 是被放到 执行栈 中,那 异步任务运行时以及定义时 是被放到哪里呢?

2. macrotask 和 microtask

在异步任务中,又分为两类任务:

  • macrotask:宏任务
  • microtask:微任务

要回答上面这个问题,首先了解一下 macrotaskmicrotask

2.1. macrotask 宏任务

以下派发的任务就是 macrotask

  • setTimeout
  • setInterval
  • setImmediate
  • I/O(如:网络请求)
  • UI rendering

每一个 Event Loop 都有一个 macrotask 队列

setTimeout(function foo() {
  console.log('foo');
}, 1000);

setTimeout(function bar() {
  console.log('foo');
}, 2000);
  • foo() macrotask 在 1000ms 后被推入 macrotask 队列
  • bar() macrotask 在 2000ms 后被推入 macrotask 队列

2.2. microtask 微任务

以下派发的任务时 microtask:

  • process.nextTick
  • promises
  • Object.observe
  • MutationObserver

每一个 Event Loop 都有一个 microtask 队列

// 调用 Promise 构造函数时,传入的回调是会立即调用的,所以 foo 不算 microtask
// new Promise(function foo(resolve) {
//   console.log('foo');
//   resolve();
// });

// 调用 Promise 构造函数时,传入的回调是会立即调用的,所以 bar 不算 microtask
// new Promise(function bar(resolve) {
//   console.log('bar');
//   resolve();
// });

Promise.resolve()
  .then(function foo() {
    console.log('promise1');
  })
  .then(function bar() {
    console.log('promise2');
  });
  • foo() microtask 被推入 microtask 队列
  • bar() microtask 被推入 microtask 队列

所以,现在来看看上面的问题:

  • 异步任务 运行时 以及 定义时 是被放到哪里呢?
    答:
  • 如果 异步任务 属于 macrotask,则在 定义时 被放到 macrotask 队列 中;如果 异步任务 属于 microtask,则在 定义时 被放到 microtask 队列

上面只回答了 异步任务 定义时 被放置的位置。接下来通过 Event Loop 来回答 异步任务运行时 会在哪里。

3. Event Loop

上面讲了:

  • 同步任务:同步任务在 代码层面,存在 定义状态执行状态。在 执行状态 时被推入 执行栈 中执行
  • 异步任务:异步任务在 代码层面,只存在 定义状态。异步任务在 定义时,根据异步任务的类型,被推入不同的队列中
    • macrotask 宏任务
    • microtask 微任务
  • 执行栈:所有的任务都会在 运行时 被推入到 执行栈 中执行

但上面还留下了一个问题:异步任务运行时 会在哪里?

这个时候,就需要知道 Event Loop 了。

Event Loop 翻译成中文为 事件循环循环 二字,就说明了 Event Loop 表示是一个在不断 循环 的过程。

这个 循环 包含了以下过程:

  1. 检查 执行栈 中的所有任务是否全部执行完毕了。若未执行完,则继续执行;否则进入下一步
  2. 检查 microtask 队列 是否存在 microtask。若存在 microtask,则将 microtask 队列 中的所有 microtask 都推入 执行栈 中执行(否则进入下一步)。执行完毕后,进入下一步
  3. 检查 macrotask 队列 中是否存在 macrotask。若存在 macrotask,则将 macrotask 队列 中的 第一个 出队,推入到 执行栈 中执行(否则进入第 1 步,一个 Event Loop 完成)。执行完毕后,进入第 1 步(一个 Event Loop 完成)

event-loop2

  • 一个 Event Loop,包含了上面三个步骤

所以,现在可以回答上面的问题了:异步任务运行时 会经过 Event Loop,且被推入到 执行栈 中。

4. 总结

Event Loop(事件循环)就是一个对 异步任务 进行 协调的过程

5. 参考

css3 动画(三)animation 简介

css3 动画(三)animation 简介

前言

上一篇中,总结了一下 transition 以及 cubic-bezier()。本篇中,将介绍 css3 动画中的更为灵活的动画属性:animation。

animation

css3 中的 animation 属性是一系列配置的简写形式,其子属性有:

animation-name

animation-name 表示的是关键帧的名称,那么如何定义关键帧呢?使用 @keyframes。

@Keyframes

@Keyframes 是定义 css3 animation 动画的关键所在。通过定义每一帧的样式状态,比 transition 能更好地控制中间过程。假如说 transition 只能定义 “两帧” 的状态,则 animation 可以定义 “n 帧(n >= 2)” 的状态。

语法

@Keyframes + 名称 { // 关键帧样式... }”

@keyframes move {
  from {
    width: 100px;
  }
  to {
    width: 200px;
  }
}
/* 或 */
@keyframes move {
  0% {
    width: 100px;
  }
  100% {
    width: 200px;
  }
}

总结

其实 animation 也并不复杂,其有 8 个子属性。

下一篇:css3 动画(四)animation.css 源码分析(通过阅读 animation.css 动画库的源码,来提高对 css3 中 animation 属性的认识)

github flow 工作流程

github flow 工作流程

前言

如何参加一个 github 开源项目来贡献自己的代码呢?下面来介绍一下 github flow 工作流程。

一、流程

1. fork github 开源项目

在开源项目首页中,可以在右上角找个 fork 按钮。点击 fork 按钮,将该开源项目 fork 到自己的项目列表中。

2. git clone fork 好的项目

将 fork 完之后的代码 clone 到本地。

3. 在本地进行 feature 开发或 bug fix

4. 开发完成之后,将代码推到 github

5. 在 fork 好的项目首页,有一个 pull request 按钮,点击。点击之后,按照页面的提示,一步一步走下去即可

二、如何同步 fork 好的开源项目

有时候,我们需要将开源项目原仓库的代码同步到 fork 的仓库,该怎么做?

  1. 在本地项目中,给 fork 的项目添加上游仓库:
git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git
  • 可通过 git remote -v 查看远程仓库地址和上游仓库地址
  1. fetch 上游仓库 master 分支
git fetch upstream master
  1. 合并上游 master 分支到本地 master 分支
git merge upstream/master
  • 若不在主分支,则 git checkout master 切换到主分支
  1. 将 merge 好的 master 分支代码 push 到远程仓库
git push origin master
  1. 完成同步!!!

css3 动画(二)贝塞尔曲线

css3 动画(二)贝塞尔曲线

前言

上一篇 css3 动画(一) transition 中,介绍了 transition 的用法。其中 transition 包含四个可设置的属性:

  1. 有过渡效果的属性
  2. 过渡时长
  3. 过渡函数?
  4. 过渡时延

其中,1、2 以及 4 都挺好理解的,但是 3 是个什么东西?其实 3 是 css3 中的 timing-function,其中 3 有两种类型的值:

本篇就总结一下 cubic-bezier(x1, y1, x2, y2):立方贝塞尔曲线

贝塞尔曲线简介

贝塞尔曲线(Bezier curve)是计算机图形学中重要的参数曲线,它通过一个方程来描述一条曲线。根据方程的最高阶数,可以分为线性贝塞尔曲线、二次贝塞尔曲线、三次贝塞尔曲线以及更高次的贝塞尔曲线。
贝塞尔曲线扫盲

css3 中使用的 cubic-bezier() 函数,是一个 三次贝塞尔曲线函数

三次贝塞尔曲线中四个点,在 cubic-bezier() 中:

  • 第一个点 p0(0, 0)最后一个点 p3(1, 1)是固定坐标的
  • p1(x1, y1)p2(x2, y2) 是传入 cubic-bezier() 函数中的参数的。其中 x∈[0, 1],y 可以不在 [0, 1] 区间内,但最大值最好不要大于 1.5,最小值不要小于 -0.5
  • 0 和 1 分别表示 0% 和 100%
    cubic-bezier(x1, y1, x2, y2) 接受的参数便是 p1(x1, h1) 和 p2(x2, y2) 的坐标。

那我们怎么获取我们想要的贝塞尔曲线呢?进这个 网站

css3 贝塞尔曲线代表的含义

在上面那个 网站 中,我们可以通过拖拽 p1 和 p2 点,来改变两点的坐标,从而产生一条曲线。

那么这条曲线代表什么含义呢?

  • 横坐标:时间。时间是匀速增加的
  • 纵坐标:进度。随着时间的增加,进度也会增加
  • 斜率:速度
    由于 时间是匀速增加的,进度增加的快慢是受斜率(速度)影响的。所以这是一条表示进度变化快慢的速度曲线

这个 进度 在 css 中,实际指的就是样式变化前后的值。如:

  • width 从 100px 变为 200px,纵坐标的起点就为 100px,终点为 200px
  • opacity 从 0 变为 1,纵坐标的起点就为 0,终点为 1
  • ...

transition + cubic-bezier() 实现平抛动画

最终效果如下:
https://codepen.io/reai3041/pen/RBbwzo

分析

我们知道,平抛运动可以分解为两个方向的运动:

  • 水平方向:匀速运动
  • 垂直方向:加速度不变的匀加速运动

这样,我们就可以分解为水平和垂直方向上的 过渡效果

<div class="ball"></div>
.ball {
  width: 10px;
  height: 10px;
  border: 1px solid #000;
  border-radius: 50%;
  position: absolute;
  left: 80px;
  top: 30px;
}
/* 终点 */
.ball.end {
  left: 180px;
  top: 230px;
  transition: left 0.2s linear, top 0.2s cubic-bezier(0.48, 0, 0.94, 0.28);
}

通过改变 left 和 top 值:

  • left 应用 linear 速度曲线(匀速)
  • top 应用 cubic-bezier(.48,0,.94,.28) 速度曲线(加速度不变的加速运动)
    来获得平抛运动的动画效果

其中,cubic-bezier() 函数的参数,可以在 网站 里自定义点的位置,然后得到自己想要的速度变化曲线。

在平抛运动垂直方向的速度曲线大概是这样子的:

clipboard.png

这样,我们就知道了 cubic-bezier() 函数的参数(图片中的这条曲线,其实就可以看做是实际的平抛的曲线)

代码及效果:
https://codepen.io/reai3041/pen/RBbwzo

总结

css3 中的贝塞尔曲线其实很简单:一条以 时间为横坐标,以 进度为纵坐标和速度相关 的曲线,它表示了 过渡/动画 中间状态的 变化快慢

下一篇:css3 动画(三)animation 简介

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.