深度优先

这个家伙好懒,除了文章什么都没留下

0%

转自:https://segmentfault.com/a/1190000008653690

虽然不是做前端的,但是偶尔搭把手还是可以的。

今天遇到的一个坑:


Angular 的口号是 - “一套框架,多种平台。同时适用手机与桌面 (One framework.Mobile & desktop.)”,即 Angular 是支持开发跨平台的应用,比如:Web 应用、移动 Web 应用、原生移动应用和原生桌面应用等。

为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异,统一了 API 接口。如定义了抽象类 Renderer 、抽象类 RootRenderer 等。此外还定义了以下引用类型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。下面我们就来分析一下 ElementRef 类:

ElementRef的作用

在应用层直接操作 DOM,就会造成应用层与渲染层之间强耦合,导致我们的应用无法运行在不同环境,如 web worker 中,因为在 web worker 环境中,是不能直接操作 DOM。有兴趣的读者,可以阅读一下 Web Workers 中支持的类和方法这篇文章。通过 ElementRef 我们就可以封装不同平台下视图层中的 native 元素 (在浏览器环境中,native 元素通常是指 DOM 元素),最后借助于 Angular 提供的强大的依赖注入特性,我们就可以轻松地访问到 native 元素。

ElementRef的定义

1
2
3
4
5
export class ElementRef {
public nativeElement: any;
constructor(nativeElement: any) { this.nativeElement = nativeElement; }
}

ElementRef的应用

我们先来介绍一下整体需求,我们想在页面成功渲染后,获取页面中的 div 元素,并改变该 div 元素的背景颜色。接下来我们来一步步,实现这个需求。

首先我们要先获取 div 元素,在文中 “ElementRef 的作用” 部分,我们已经提到可以利用 Angular 提供的强大的依赖注入特性,获取封装后的 native 元素。在浏览器中 native 元素就是 DOM 元素,我们只要先获取 my-app元素,然后利用 querySelector API 就能获取页面中 div 元素。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Component, ElementRef } from '@angular/core';

@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<div>Hello {{ name }}</div>
`,
})
export class AppComponent {

name: string = 'Semlinker';

constructor(private elementRef: ElementRef) {
let divEle = this.elementRef.nativeElement.querySelector('div');
console.dir(divEle);
}
}

运行上面代码,在控制台中没有出现异常,但是输出的结果却是 null 。什么情况 ? 没有抛出异常,我们可以推断 this.elementRef.nativeElement 这个对象是存在,但却找不到它的子元素,那应该是在调用构造函数的时候,my-app 元素下的子元素还未创建。那怎么解决这个问题呢 ?沉思中… ,不是有 setTimeout 么,我们在稍微改造一下:

1
2
3
4
5
6
7
constructor(private elementRef: ElementRef) {
setTimeout(() => { // 此处需要使用箭头函数哈,你懂的...
let divEle = this.elementRef.nativeElement.querySelector('div');
console.dir(divEle);
}, 0);
}

更新一下代码,此时控制台成功输出了 div 。为什么添加个 setTimeout 就能成功获取到想要的 div 元素呢?此处就不展开了,有兴趣的读者可以参考 - What the heck is the event loop anyway? 这个演讲的示例。

问题解决了,但感觉不是很优雅 ?有没有更好的方案,答案是肯定的。Angular 不是有提供组件生命周期的钩子,我们可以选择一个合适的时机,然后获取我们想要的 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
import { Component, ElementRef, AfterViewInit } from '@angular/core';

@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<div>Hello {{ name }}</div>
`,
})
export class AppComponent {

name: string = 'Semlinker';

// 在构造函数中 this.elementRef = elementRef 是可选的,编译时会自动赋值
// function AppComponent(elementRef) { this.elementRef = elementRef; }
constructor(private elementRef: ElementRef) { }

ngAfterViewInit() { // 模板中的元素已创建完成
console.dir(this.elementRef.nativeElement.querySelector('div'));
// let greetDiv: HTMLElement = this.elementRef.nativeElement.querySelector('div');
// greetDiv.style.backgroundColor = 'red';
}
}

运行一下上面的代码,我们看到了意料中的 div 元素。我们直接选用 ngAfterViewInit 这个钩子,不要问我为什么,因为它看得最顺眼咯。不过我们后面也会有专门的文章,详细分析一下 Angular 组件的生命周期。成功取到 div 元素,就剩下的事情就好办了,直接通过 style 对象设置元素的背景颜色。

功能虽然已经实现了,但还有优化的空间么?在 Angular 2 Decorators part - 2 文章中我们有谈到 Angular 内置的属性装饰器,如 @ContentChild、 @ContentChildren、@ViewChild、@ViewChildren 等。相信读者看完后,已经猜到我们的优化方案了。具体示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<div #greet>Hello {{ name }}</div>
`,
})
export class AppComponent {
name: string = 'Semlinker';

@ViewChild('greet')
greetDiv: ElementRef;

ngAfterViewInit() {
this.greetDiv.nativeElement.style.backgroundColor = 'red';
}
}

是不是感觉瞬间高大上了,不过先等等,上面的代码是不是还有进一步的优化空间呢 ?我们看到设置 div 元素的背景,我们是默认应用的运行环境在是浏览器中。前面已经介绍了,我们要尽量减少应用层与渲染层之间强耦合关系,从而让我们应用能够灵活地运行在不同环境。最后我们来看一下,最终优化后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component, ElementRef, ViewChild, AfterViewInit, Renderer } from '@angular/core';

@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<div #greet>Hello {{ name }}</div>
`,
})
export class AppComponent {
name: string = 'Semlinker';

@ViewChild('greet')
greetDiv: ElementRef;

constructor(private elementRef: ElementRef, private renderer: Renderer) { }

ngAfterViewInit() {
// this.greetDiv.nativeElement.style.backgroundColor = 'red';
this.renderer.setElementStyle(this.greetDiv.nativeElement, 'backgroundColor', 'red');
}
}

作者: 阮一峰

日期: 2015年7月10日

地址:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

网页布局(layout)是 CSS 的一个重点应用。

布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

2009年,W3C 提出了一种新的方案—-Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

Flex 布局将成为未来布局的首选方案。本文介绍它的语法,下一篇文章给出常见布局的 Flex 写法。网友 JailBreak 为本文的所有示例制作了 Demo,也可以参考。

以下内容主要参考了下面两篇文章:A Complete Guide to FlexboxA Visual Guide to CSS3 Flexbox Properties

一、Flex 布局是什么?

Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。

任何一个容器都可以指定为 Flex 布局。

1
2
3
4
5
 
.box{
display: flex;
}

行内元素也可以使用 Flex 布局。

1
2
3
4
5
 
.box{
display: inline-flex;
}

Webkit 内核的浏览器,必须加上 -webkit 前缀。

1
2
3
4
5
6
 
.box{
display: -webkit-flex; /* Safari */
display: flex;
}

注意,设为 Flex 布局以后,子元素的 floatclearvertical-align 属性将失效。

二、基本概念

采用 Flex 布局的元素,称为 Flex 容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称”项目”。

容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做 main start ,结束位置叫做 main end ;交叉轴的开始位置叫做 cross start ,结束位置叫做 cross end

项目默认沿主轴排列。单个项目占据的主轴空间叫做 main size ,占据的交叉轴空间叫做 cross size

三、容器的属性

以下6个属性设置在容器上。

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

3.1 flex-direction属性

flex-direction 属性决定主轴的方向(即项目的排列方向)。

1
2
3
4
5
 
.box {
flex-direction: row | row-reverse | column | column-reverse;
}

它可能有4个值。

  • row (默认值):主轴为水平方向,起点在左端。
  • row-reverse :主轴为水平方向,起点在右端。
  • column :主轴为垂直方向,起点在上沿。
  • column-reverse :主轴为垂直方向,起点在下沿。

3.2 flex-wrap属性

默认情况下,项目都排在一条线(又称”轴线”)上。 flex-wrap 属性定义,如果一条轴线排不下,如何换行。

1
2
3
4
5
 
.box{
flex-wrap: nowrap | wrap | wrap-reverse;
}

它可能取三个值。

(1) nowrap (默认):不换行。

(2) wrap :换行,第一行在上方。

(3) wrap-reverse :换行,第一行在下方。

3.3 flex-flow

flex-flow 属性是 flex-direction 属性和 flex-wrap 属性的简写形式,默认值为 row nowrap

1
2
3
4
5
 
.box {
flex-flow: <flex-direction> || <flex-wrap>;
}

3.4 justify-content属性

justify-content 属性定义了项目在主轴上的对齐方式。

1
2
3
4
5
 
.box {
justify-content: flex-start | flex-end | center | space-between | space-around;
}

它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。

  • flex-start (默认值):左对齐
  • flex-end :右对齐
  • center : 居中
  • space-between :两端对齐,项目之间的间隔都相等。
  • space-around :每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

3.5 align-items属性

align-items 属性定义项目在交叉轴上如何对齐。

1
2
3
4
5
 
.box {
align-items: flex-start | flex-end | center | baseline | stretch;
}

它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

  • flex-start :交叉轴的起点对齐。
  • flex-end :交叉轴的终点对齐。
  • center :交叉轴的中点对齐。
  • baseline : 项目的第一行文字的基线对齐。
  • stretch (默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

3.6 align-content属性

align-content 属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

1
2
3
4
5
 
.box {
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}

该属性可能取6个值。

  • flex-start :与交叉轴的起点对齐。
  • flex-end :与交叉轴的终点对齐。
  • center :与交叉轴的中点对齐。
  • space-between :与交叉轴两端对齐,轴线之间的间隔平均分布。
  • space-around :每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
  • stretch (默认值):轴线占满整个交叉轴。

四、项目的属性

以下6个属性设置在项目上。

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex
  • align-self

4.1 order属性

order 属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。

1
2
3
4
5
 
.item {
order: <integer>;
}

4.2 flex-grow属性

flex-grow 属性定义项目的放大比例,默认为 0 ,即如果存在剩余空间,也不放大。

1
2
3
4
5
 
.item {
flex-grow: <number>; /* default 0 */
}

如果所有项目的 flex-grow 属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的 flex-grow 属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

4.3 flex-shrink属性

flex-shrink 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

1
2
3
4
5
 
.item {
flex-shrink: <number>; /* default 1 */
}

如果所有项目的 flex-shrink 属性都为1,当空间不足时,都将等比例缩小。如果一个项目的 flex-shrink 属性为0,其他项目都为1,则空间不足时,前者不缩小。

负值对该属性无效。

4.4 flex-basis属性

flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为 auto ,即项目的本来大小。

1
2
3
4
5
 
.item {
flex-basis: <length> | auto; /* default auto */
}

它可以设为跟 widthheight 属性一样的值(比如350px),则项目将占据固定空间。

4.5 flex属性

flex 属性是 flex-grow , flex-shrinkflex-basis 的简写,默认值为 0 1 auto 。后两个属性可选。

1
2
3
4
5
 
.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

该属性有两个快捷值: auto ( 1 1 auto ) 和 none ( 0 0 auto )。

建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

4.6 align-self属性

align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖 align-items 属性。默认值为 auto ,表示继承父元素的 align-items 属性,如果没有父元素,则等同于 stretch

1
2
3
4
5
 
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}

该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

刚刚无意间从SwaggerUI汉化上发现一个简单将Html页面多语言化的方法。

具体操作很简单,只需引一下JS文件,具体代码如下

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<script src="http://libs.baidu.com/jquery/1.8.1/jquery.min.js"></script>
<script src="./zh-cn.bilingualism.js"></script>
<body>
<h3>Test</h3>
<a href="#" data-sw-translate>Show/Hide</a>
<h6 data-sw-translate>Show/Hide</h6>
<h6 data-sw-translate>Hello World</h6>
</body>
</html>

JavaScript:

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
'use strict';

/**
* Html页面多语言化。
*
* 在Html页面中只需先引用一个JQuery文件:
* 例如:<script src="http://libs.baidu.com/jquery/1.8.1/jquery.min.js"></script>
*
* 在引用本JS文件
* 例如:<script src="./zh-cn.bilingualism.js"></script>
*
* 如果你想多语言化一个Html页面,你应该做两件事:
* 1. 在HTML标签中,加入 data-sw-translate="" 标记
* 例如:<h6 data-sw-translate="">Hello World</h6>
* 2. 然后在下面的 window.SwaggerTranslator.learn 中添加中英文对应关系
* 添加: "Hello World": "你好,世界",
*/
window.SwaggerTranslator = {
_words: [],

translate: function () {
var $this = this;
$('[data-sw-translate]').each(function () {
$(this).html($this._tryTranslate($(this).html()));
$(this).val($this._tryTranslate($(this).val()));
$(this).attr('title', $this._tryTranslate($(this).attr('title')));
});
},

_tryTranslate: function (word) {
return this._words[$.trim(word)] !== undefined ? this._words[$.trim(word)] : word;
},

learn: function (wordsMap) {
this._words = wordsMap;
}
};


/* jshint quotmark: double */
window.SwaggerTranslator.learn({
"Warning: Deprecated": "警告:已过时",
"Implementation Notes": "实现备注",
"Response Class": "响应类",
"Status": "状态",
"Parameters": "参数",
"Parameter": "参数",
"Value": "值",
"Description": "描述",
"Parameter Type": "参数类型",
"Data Type": "数据类型",
"Response Messages": "响应消息",
"HTTP Status Code": "HTTP状态码",
"Reason": "原因",
"Response Model": "响应模型",
"Request URL": "请求URL",
"Response Body": "响应体",
"Response Code": "响应码",
"Response Headers": "响应头",
"Hide Response": "隐藏响应",
"Headers": "头",
"Try it out!": "试一下!",
"Show/Hide": "显示/隐藏",
"List Operations": "显示操作",
"Expand Operations": "展开操作",
"Raw": "原始",
"can't parse JSON. Raw result": "无法解析JSON. 原始结果",
"Model Schema": "模型架构",
"Model": "模型",
"apply": "应用",
"Username": "用户名",
"Password": "密码",
"Terms of service": "服务条款",
"Created by": "创建者",
"See more at": "查看更多:",
"Contact the developer": "联系开发者",
"api version": "api版本",
"Response Content Type": "响应Content Type",
"fetching resource": "正在获取资源",
"fetching resource list": "正在获取资源列表",
"Explore": "浏览",
"Show Swagger Petstore Example Apis": "显示 Swagger Petstore 示例 Apis",
"Can't read from server. It may not have the appropriate access-control-origin settings.": "无法从服务器读取。可能没有正确设置access-control-origin。",
"Please specify the protocol for": "请指定协议:",
"Can't read swagger JSON from": "无法读取swagger JSON于",
"Finished Loading Resource Information. Rendering Swagger UI": "已加载资源信息。正在渲染Swagger UI",
"Unable to read api": "无法读取api",
"from path": "从路径",
"server returned": "服务器返回",
"Hello World": "你好,世界"
});


$(function () {
window.SwaggerTranslator.translate();
});