let 的作用域与 const 命令相同:只在声明所在的块级作用域内有效。且不存在变量提升 。
1.1 let
let 所声明的变量,可以改变。
let a=123 a=456 // 正确,可以改变 let b=[123] b=[456] // 正确,可以改变
1.2 const
const 声明一个只读的常量。一旦声明,常量的值就不能改变。
简单类型的数据(数值、字符串、布尔值),不可以变动
const a=123 a=456 // 报错,不可改变 const b=[123] b=[456] // 报错,不可以重新赋值,不可改变
复合类型的数据(主要是对象和数组),可以这样子变动
const a=[123] a.push(456) // 成功 const b={} b.name='demo' // 成功
1.3 不存在变量提升
{ let a=10; var b=1; } a // ReferenceError: a is not defined. b // 1
所以 for循环的计数器,就很合适使用 let 命令。
let a=[]; for (let i=0; i < 10; i++) { a[i]=function () { console.log(i); }; } a[6](); // 6
1.4 推荐
对于 数值、字符串、布尔值 经常会变的,用 let 声明。
对象、数组和函数用 const 来声明。
// 如经常用到的导出 函数 export const funA=function(){ // .... }
2.1 数组
一次性声明多个变量:
let [a, b, c]=[1, 2, 3]; console.log(a) // 1 console.log(b) // 2 console.log(c) // 3
结合扩展运算符:
let [head, ...tail]=[1, 2, 3, 4]; console.log(head) // 1 console.log(tail) // [2, 3, 4]
解构赋值允许指定默认值:
let [foo=true]=[]; foo // true let [x, y='b']=['a']; // x='a', y='b'
2.2 对象
解构不仅可以用于数组,还可以用于对象。
let { a, b }={ a: "aaa", b: "bbb" }; a // "aaa" b // "bbb"
数组中,变量的取值由它 排列的位置 决定;而对象中,变量必须与 属性 同名,才能取到正确的值。
对象的解构也可以指定默认值。
let {x=3}={}; x // 3 let {x, y=5}={x: 1}; x // 1 y // 5
2.3 字符串
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e]='hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
2.4 用途
2.4.1交换变量的值
let x=1; let y=2; [x, y]=[y, x];
2.4.2从函数返回多个值
// 返回一个数组 function example() { let [a, b, c]=[1, 2, 3] return [a, b, c] } let [a, b, c]=example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar }=example();
2.4.3函数参数的默认值
function funA (a=1, b=2){ return a + b; } funA(3) // 5 因为 a 是 3, b 是 2 funA(3,3) // 6 因为 a 是 3, b 是 3
2.4.4输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode }=require("source-map");
在 utils.js 中:
export const function A (){ console.log('A') } export const function B (){ console.log('B') } export const function C (){ console.log('C') }
在 组件中引用时:
import { A, B, C } from "./utils.js" //调用 A() // 输出 A
模板字符串(template string)用反引号(`)标识。
3.1 纯字符串
所有模板字符串的空格和换行,都是被保留的.
console.log(`输出值为 N, 换行`) // "输出值为 N 换行"
3.2 字符串中加变量
模板字符串中嵌入变量,需要将变量名写在 ${ } 之中
let x=1; let y=2; console.log(`输出值为:${x}`) // "输出值为:1" console.log(`输出值为:${x + y}`) // "输出值为:3"
3.3 模板字符串之中还能调用函数。
function fn() { return "Hello World"; } console.log(`输出值为:${fn()}`) // "输出值为:Hello World"
let s='Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s='Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
5.1 指数运算符
ES2016 新增了一个指数运算符(**)。
2 ** 2 // 4 2 ** 3 // 8
这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。
// 相当于 2 ** (3 ** 2) 2 ** 3 ** 2 // 512
上面代码中,首先计算的是第二个指数运算符,而不是第一个。
指数运算符可以与等号结合,形成一个新的赋值运算符(**=)。
let a=1.5; a **=2; // 等同于 a=a * a; let b=4; b **=3; // 等同于 b=b * b * b;
除了在解构中说到的函数参数的默认值,还有不少经常会用到的方法。
6. 1 rest 参数
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) { let sum=0; for (let val of values) { sum +=val; } return sum; } add(2, 5, 3) // 10
上面代码的 add 函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 报错 function f(a, ...b, c) { // ... }
6.2 箭头函数
ES6 允许使用“箭头”(=>)定义函数。
const f=v=> v; console.log('输出值:', f(3)) // 输出值: 3 // 等同于 const f=function (v) { return v; };
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
const f=()=>5 // 等同于 const f=function () { return 5 }; const sum=(num1, num2)=> num1 + num2; // 等同于 const sum=function(num1, num2) { return num1 + num2; };
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。
const sum=(num1, num2)=> { return num1 + num2; }
箭头函数的一个用处是简化回调函数。
const square=n=> n * n; // 正常函数写法 [1,2,3].map(function (x) { return x * x; }); // 箭头函数写法 [1,2,3].map(x=> x * x);
注意: 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
this 对象的指向是可变的,但是在箭头函数中,它是固定的。
function foo() { setTimeout(()=> { console.log('id:', this.id); }, 100); } let id=21; foo.call({ id: 42 }); // id: 42
上面代码中,setTimeout 的参数是一个箭头函数,这个箭头函数的定义生效是在 foo 函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时 this 应该指向全局对象window,这时应该输出 21。但是,箭头函数导致 this 总是指向函数定义生效时所在的对象(本例是{ id: 42}),所以输出的是 42。
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
7.1 数组合并的新写法。
const arr1=['a', 'b']; const arr2=['c']; const arr3=['d', 'e']; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
7.2 函数调用。
function add(x, y) { return x + y; } const numbers=[4, 4]; add(...numbers) // 8
7.3 复制数组的简便写法。
const a1=[1, 2]; // 写法一 const a2=[...a1]; a2[0]=2; a1 // [1, 2] // 写法二 const [...a2]=a1; a2[0]=2; a1 // [1, 2]
写法一更符合逻辑。
上面的两种写法,a2 都是 a1 的克隆,且不会修改原来的数组。
7.4 将字符串转为真正的数组。
[...'hello'] // [ "h", "e", "l", "l", "o" ]
7.5 数组实例的 entries(),keys() 和 values()
用 for…of 循环进行遍历,唯一的区别是 keys() 是对键名的遍历、values() 是对值的遍历,entries() 是对键值对的遍历。
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
7.6 includes()
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。ES2016 引入了该方法。
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为 0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为 -4,但数组长度为 3 ),则会重置为从 0 开始。
[1, 2, 3].includes(3, 3); // false [1, 2, 3].includes(3, -1); // true
8.1 属性和方法 的简洁表示法
let birth='2000/01/01'; const Person={ name: '张三', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('我的名字是', this.name); } };
8.2 Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target={ a: 1 }; const source1={ b: 2 }; const source2={ c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
const target={ a: 1, b: 1 }; const source1={ b: 2, c: 2 }; const source2={ c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
Object.assign 方法实行的是浅拷贝,而不是深拷贝。
const obj1={a: {b: 1}}; const obj2=Object.assign({}, obj1); obj1.a.b=2; // 修改b obj2.a.b // 2, 也改变了
上面代码中,源对象 obj1 的 a 属性的值是一个对象,Object.assign 拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构。
// 基本用法 const s=new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x=> s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4 // 去除数组的重复成员 const array=[1, 1, 2, 3, 4, 4] [...new Set(array)] // [1, 2, 3, 4]
Promise 是异步编程的一种解决方案。
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为
rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)
const someAsyncThing=function(flag) { return new Promise(function(resolve, reject) { if(flag){ resolve('ok'); }else{ reject('error') } }); }; someAsyncThing(true).then((data)=> { console.log('data:',data); // 输出 'ok' }).catch((error)=>{ console.log('error:', error); // 不执行 }) someAsyncThing(false).then((data)=> { console.log('data:',data); // 不执行 }).catch((error)=>{ console.log('error:', error); // 输出 'error' })
上面代码中,someAsyncThing 函数成功返回 ‘OK’, 失败返回 ‘error’, 只有失败时才会被 catch 捕捉到。
最简单实现:
// 发起异步请求 fetch('/api/todos') .then(res=> res.json()) .then(data=> ({ data })) .catch(err=> ({ err }));
来看一道有意思的面试题:
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i==9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5);
这道题应该考察 JavaScript 的运行机制的。
首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。
然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。
然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。
因此,应当先输出 5,然后再输出 4 。
最后在到下一个 tick,就是 1 。
答案:“2 3 5 4 1”
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数的使用方式,直接在普通函数前面加上 async,表示这是一个异步函数,在要异步执行的语句前面加上 await,表示后面的表达式需要等待。async 是 Generator 的语法糖
1. async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。
async function f() { return 'hello world'; } f().then(v=> console.log(v)) // "hello world"
上面代码中,函数 f 内部 return 命令返回的值,会被 then 方法回调函数接收到。
2. async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到。
async function f() { throw new Error('出错了'); } f().then( result=> console.log(result), error=> console.log(error) ) // Error: 出错了
3. async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数。
下面是一个例子:
async function getTitle(url) { let response=await fetch(url); let html=await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(function(res) { console.log(res) }) // "ECMAScript 2017 Language Specification"
上面代码中,函数 getTitle 内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行 then 方法里面的 console.log。
4. 在 vue 中,我们可能要先获取 token ,之后再用 token 来请求用户数据什么的,可以这样子用:
methods:{ getToken() { return new Promise((resolve, reject)=> { this.$http.post('/token') .then(res=> { if (res.data.code===200) { resolve(res.data.data) } else { reject() } }) .catch(error=> { console.error(error); }); }) }, getUserInfo(token) { return new Promise((resolve, reject)=> { this.$http.post('/userInfo',{ token: token }) .then(res=> { if (res.data.code===200) { resolve(res.data.data) } else { reject() } }) .catch(error=> { console.error(error); }); }) }, async initData() { let token=await this.getToken() this.userInfo=this.getUserInfo(token) }, }
import 导入模块、export 导出模块
// example2.js // 导出默认, 有且只有一个默认 export default const example2={ name : 'my name', age : 'my age', getName=function(){ return 'my name' } } //全部导入 // 名字可以修改 import people from './example2.js' -------------------我是一条华丽的分界线--------------------------- // example1.js // 部分导出 export let name='my name' export let age='my age' export let getName=function(){ return 'my name'} // 导入部分 // 名字必须和 定义的名字一样。 import {name, age} from './example1.js' //有一种特殊情况,即允许你将整个模块当作单一对象进行导入 //该模块的所有导出都会作为对象的属性存在 import * as example from "./example1.js" console.log(example.name) console.log(example.age) console.log(example.getName()) -------------------我是一条华丽的分界线--------------------------- // example3.js // 有导出默认, 有且只有一个默认,// 又有部分导出 export default const example3={ birthday : '2018 09 20' } export let name='my name' export let age='my age' export let getName=function(){ return 'my name'} // 导入默认与部分 import example3, {name, age} from './example1.js'
总结:
1.当用 export default people 导出时,就用 import people 导入(不带大括号)
2.一个文件里,有且只能有一个 export default。但可以有多个 export。
3.当用 export name 时,就用 import { name }导入(记得带上大括号)
4.当一个文件里,既有一个 export default people, 又有多个 export name 或者 export age 时,导入就用 import people, { name, age }
5.当一个文件里出现 n 多个 export 导出很多模块,导入时除了一个一个导入,也可以用 import * as example
对于 Class ,小汪用在 react 中较多。
13.1基本用法:
class Point { constructor(x, y) { this.x=x; this.y=y; } sum() { console.log(this.x + this.y) } } let f=new Point(10, 20); f.sum() // 30
13.2 继承
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color=color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } } let g=new ColorPoint(10, 11, 'red')
上面代码中,constructor 方法和 toString 方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的 this 对象。
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
class Point { /* ... */ } class ColorPoint extends Point { constructor() { } } let cp=new ColorPoint(); // ReferenceError
上面代码中,ColorPoint 继承了父类 Point,但是它的构造函数没有调用 super 方法,导致新建实例时报错。
参考:https://mp.weixin.qq.com/s/TCpXWORIqZk34gBVIid3vA
明 发自 凹非寺
量子位 报道 | 公众号 QbitAI
镜子里的人,是人吗?对于计算机视觉系统来说:是。
大部分系统也不考虑镜子因素,它们很难分清楚镜中人。
镜子作为日常生活中非常重要的物体无处不在,不仅能够反射光线,能呈现出周围物体或者场景的镜像。
这就导致计算机视觉系统或者机器人一旦遇到有镜子的场景,性能就会大幅下降,可以说是遇到了克星。
怎么办?来自大连理工、鹏城实验室和香港城市大学的研究团队提出了一个方法。
他们发表了一篇名为Where Is My Mirror?的论文,已经被ICCV2019收录。
在这篇论文中,他们构建了一个大规模的镜像数据集,并提出了一种从输入图像中分割镜子的新方法。
不仅能够准确识别并分割出场景中的镜子,还能够消除由于镜子反射所导致的对于场景的错误理解,并帮助一些计算机视觉任务(例如深度估计和目标检测)提升鲁棒性。
他们说,这是首个解决镜子分割的方法,经过大量的实验表明,性能表现比最先进的检测和分割方法都要好。
未来,他们的目标是检测出现在城市街道上的镜子,这对户外执行的视觉任务——自动驾驶和无人机导航——都有助益。
Where Is My Mirror?
对于计算机视觉系统来说,镜子反射的内容与镜子外部的内容(即周围环境)非常相似,它们很难区分出来,更不用说从一个背景中自动分割镜子了。
从这点来看,系统是比不上人的。大多数人类,通常能很好地察觉镜子的存在。
向人类学习,成了这篇论文的突破点。研究人员观察到,人们识别镜像中的内容,通常会从边界入手,观察其不连续性。
因此,这个问题的一个直接的解决方案,是应用低层次的特征,比如颜色和纹理变化,来检测镜子边界。
但如果一个镜子前面有物体遮挡,这个方法就不管用了,比如这样的情况:
单靠检测镜子边界很难将对象的反射与对象本身分离开,也需要语义,即上下文对比信息来进行进行分割。
基于此,研究人员从两个方面来解决镜子分割问题:数据和神经网络。
自建数据集
因为这一领域之前并未有太多人关注,自然也没有可用数据集。
于是他们就自己动手,创建数据集MSD,包含4018对包含镜子和相应的手动注释的蒙版图像。
其中,有3677张来自室内场景,341张来自室外场景,基本上涵盖了生活中常见的出现镜子的场景:化妆台、装饰品、浴室、路面镜子、卧室、办公室、花园、街道和停车场。
最后有3063张图像用于训练,955张图像用于测试。
怎么找镜子?
论文中提出的镜子分割网络MirrorNet的架构,以单幅图像为输入,通过特征提取网络(FEN)提取多层特征。
然后,将最深层的、语义丰富的特征输入到所提出的上下文对比特征提取(CCFE)模块中,学习上下文对比特征,通过检测对比出现的分界线,用初始的粗糙的镜子分割图来定位镜子。
这一镜子分割图作为注意力图,用于抑制非镜子区域上一层 FEN 特征的特征噪声,使上一层能够集中学习候选镜子区域的鉴别特征。
通过这种方式,MirrorNet逐步利用上下文对比信息以从粗到精的方式细化镜子区域。最后,对最粗的网络输出进行上行采样,得到原始的图像分辨率作为输出。
镜子在这里
自建数据集提出的MirrorNet效果怎么样?
研究人员采用了相关领域中常用的5个度量(即语义分割、显著目标检测和阴影检测) ,对镜子分割性能进行定量评估。
比较对象也都是目标分割领域先进的模型,比如Mask RCNN、R3Net等等。
从这些指标来看,MirrorNet表现都是最佳。
一次跨越南北的合作
这一论文来自大连理工、鹏城实验室和香港城市大学,是一次跨越南北的合作。
第一作者有两位,分别是杨鑫和梅海洋。
杨鑫,大连理工大学计算机学院副教授、博士生导师、学校学科办建设副主任。本科毕业于吉林大学计算机学院,于浙江大学-美国加州大学戴维斯分校计算机学院进行博士生联合培养,获工学博士学位,香港城市大学博士后。
梅海洋,大连理工大学在读博士生,本科也毕业于大连理工大学。研究兴趣为图像处理、计算机视觉和深度学习。
梅海洋介绍称,他们团队围绕镜子,用了一年半的时间进行课题调研、确定问题、制作数据集、设计模型、优化模型,研究成果最终被ICCV2019接收。
后续将围绕城市间建筑表面的镜子来展开研究,以此来进一步扩展问题,缓解各种场景下镜子对于其他视觉任务的影响,提高应用价值。
最后,梅海洋说,关于这一研究的数据集和代码将会开源,希望广大的研究者们能够一起加入到这个问题的研究中~
如果你对这一研究感兴趣,请收好传送门:
https://mhaiyang.github.io/ICCV2019_MirrorNet/index.html
— 完 —
诚挚招聘
量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复“招聘”两个字。
量子位 QbitAI · 头条号签约作者
?'?' ? 追踪AI技术和产品新动态
CSS Gird已经被W3C纳入到css3的一个布局模块中,被称为CSS Grid Layout Module,一般习惯称为网格布局。
网格布局可以将应用程序分割成不同的空间,定义它们的大小、位置和层级。
简单来说,网格布局就像表格一样可以让元素按列和行对齐排列,不同的是,网格布局没有内容结构,比如一个网格布局的子元素可以定位自己的位置,可以是实现类似定位的效果。
兼容性:
测试地址:https://www.caniuse.com/
可以看到几大浏览器都已经支持了Grid布局,接下来我们来一步步的来玩转Grid布局
grid vs flex
我们知道flex和grid都是css3新的布局方式,如果浏览器都支持两种布局,你会选择那种呢?当我们了解两者以后就能做出正确的选择了。
flex布局是一维布局,grid布局是二维布局。
网格容器和网格项
我们知道给一个元素设置了display:flex就指定了flex弹性布局,实现grid布局一样简单,给元素设置display:grid就可以了。
container 就是一个网格容器,里面的item就是网格项
网格线 grid lines
网格线组成了网格,是网格水平和垂直的分界线。
网格轨道 grid track
就是两条网格线之间的空间,可以理解成表格里面的行或者列,网格里面为grid-row和grid-column,网格轨道可以设置大小,来控制高度或者宽度。
上图grid-column2和grid-column3之间的区域就是一个网格轨道
网格单元格 grid cell
就是四条网格线之间的空间,是最小的单位。
网格区域 area
也是四条网格线组成的空间,可能包含一个或者多个单元格。
实现一个grid布局
了解网格个相关概念,接下来我们来创建一个简单的grid布局。
上面我们说网格轨道的时候说了可以给网格轨道设置大小,可以控制高度或者宽度。
我们来分析下上面的css
1、给grid元素设置了 display: grid来声明使用grid布局
2、使用grid-template-columns来设置列宽,分别为 300px 200px 150px
3、使用grid-template-rows来设置行高,分别为150px 100px
以上代码我们是实现了一个两行三列的grid布局,此时浏览器显示如下
单位 fr
grid-template-columns和grid-template-rows不只是可以设置具体的数值,还可以设置百分比、rem一类的,还可以设置一个新单位 fr,它是来干什么的呢?
我们先把上面demo里面的css文件改下
展示如下:
以上实现了弹性布局,fr用来实现弹性布局, 我们这里使用里repeat(2, 1fr),表示重复两次,都是1fr。
grid-gap 网格项间隙
css修改如下
展示如下
网格布局属性 grid-placement-properties
网格布局属性主要用来放置容器内的网格项目,就是单一项目的位置。网格布局属性主要有以下四个属性:
1、grid-column-start 设置垂直方向的开始位置网格线 2、grid-column-end 设置垂直方向的结束位置网格线 3、grid-row-start 设置水平方向的开始位置网格线 4、grid-row-end 设置水平方向的结束位置网格线
以上的简写方式
1、grid-column: grid-column-start / grid-column-end 2、grid-row: grid-row-start / grid-row-end
终极简写
grid-area: grid-row-start / grid-column-start / grid-row-end / grid-colun-end
是不是有点蒙,我们可以大概看下,先来看deme
还是熟悉的html布局
先来看看我们的成果
显示网格线的图片
我们来分析下css
1、grid元素声明grid布局,grid-template-columns和grid-template-rows来创建一个两行三列的网格,但是渲染的结果却是三行三列,为什么?我们先接着往下分析
2、css文件中单独设置item-2网格项的位置,
grid-column-start:2 垂直线开始位置为2 grid-column-end:4垂直线结束位置为4 grid-row-start:1 水平线开始位置为1 grid-row-end:2 水平线结束位置为2
3、通过单独设置item-2的位置,把本身要在第一行的item-3给挤下来了,然后 3、4、5按照300 200 150 排列
4、这时候两件三列排列完了,但是还有个元素,此时剩下的元素就会独自占一行的位置,它的大小一样会按照网格容器定义的行高列宽来渲染
5、最后我们给item-6来设置了终极简写方式,
终极简写:行开始 / 列开始 / 行结束 / 列结束,然后我们把位置对应上
grid-area:3 / 1 / 4 / 4
通过设置网格项样式属性,我们可以就实现很多复杂的布局结构了。
几种布局
最后我们结合上面所学到的实现几个常见布局
1、左右固定,中间自适应
设置网格容器的 grid-template-columns: 100px 1fr 100px或者grid-template-columns: 100px auto 100px就可以实现,再简单不过了
效果:
2、九宫格
使用grid-gap设置网格项间距 使用fr来平分
显示如下
3、圣杯、双飞翼
使用grid-area设置header元素和footer元素位置,结合grid-template-columns和grid-template-rows实现布局
效果图:
*请认真填写需求信息,我们会在24小时内与您取得联系。