LuoZunhua1993

LuoZunhua1993 查看完整档案

南京编辑  |  填写毕业院校  |  填写所在公司/组织 github.com/GlennLuo1992 编辑
编辑

giser

个人动态

LuoZunhua1993 赞了文章 · 10月13日

深入了解 Flex 属性

作者:Ahmad shaded
译者:前端小智
来源:sitepoint
移动端阅读:https://mp.weixin.qq.com/s/Tw...
点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

你有没有想过 CSS 中的 flex属性如何工作? 它是 flex-growflex-shrinkflex-basis的简写。 开发中最常见的写法是flex:1,它表示 flex 项目扩展并填充可用空间。

接下来,我们来详细看看它表示是什么意思。

flex-grow 属性

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。flex-grow的值只接受一个整数。考虑下面代码:

<div class="wrapper">
  <div class="item item-1"></div>
  <div class="item item-2"></div>
  <div class="item item-3"></div>
</div>
.wrapper {
    display: flex;
    flex-wrap: wrap;
}

.item {
    flex-grow: 1;
}
注意:flex-grow会影响宽度或高度,具体取决于flex-direction属性。 对于以下示例,默认的flex-direction的值都是row

在不使用flex-grow的情况下,flex 项目的宽度将默认为其初始宽度。 但是,使用flex-grow: 1时,flex 项目会平均剩余可用的空间。

clipboard.png

你可能想知道,flex 项目之间的空间是如何分配的?嗯,这是个好问题,稍后会回答。

在下面的图中,是没有使用flex-grow情况。换句话说,这是它们的自然大小。

clipboard.png

要了解 flex 项目宽度的计算方式,可以参考下面的公式。

我们来计算一下文本是 CSS 的项目宽度。

clipboard.png

项目宽度 = (( flex-grow / flex-grow 总个数) * 可用空间)+ 初始项目宽度

多个 flex-grow 值

在前面的示例中,所有flex项目的flex-grow值都相同。 现在我们把第一项的flex-grow值改为2。 这们它又是如何计算? 请注意,本示例的可用空间为498px

clipboard.png

上图已经解释的很清楚,这里就不在啰嗦了,还不懂的,可以多看几次。

可以用0作为flex-grow的值吗?

当然可以!因为flex-grow属性接受整数值,所以可以使用0,可以防止 flex 项目占用可用空间的一种方式。

clipboard.png

这在边界情况下非常有用,我们希望使 flex 项目保持其初始宽度。

flex-grow 不能让 flex 项目相等

有一个常见的误解,使用flex-grow会使项目的宽度相等。这是不正确的,flex-grow的作用是分配可用空间。正如在公式中看到的,每 flex 项目的宽度是基于其初始宽度计算的(应用flex-grow之前的宽度)。

如果你想让项目的宽度相等,可以使用flex-basis,这个在接下来的部分会对此进行讲解。

flex-shrink 属性

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

考虑下面的例子:中间的项目宽度为300pxflex-shrink的值为`。如果没有足够的空间来容纳所有的项目,则允许项目缩小宽度。

clipboard.png

.item-2 {
    width: 300px;
    flex-shrink: 1;
}

在下列条件下,浏览器会保持项目宽度为300px:

  • 所有项目宽度的总和小于包装器宽度
  • 视窗宽度等于或小于项目

下面是项目在不同视口大小下的行为。

clipboard.png

如图所示,在视口宽度大于300px时,宽度为300px,少于 300px,该项目的宽度就被压缩成跟视口一样的宽度。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

flex-basis 属性

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

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

.item-1 {
    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: 50%;
}

clipboard.png

在上面的例子中,第一项的宽度为50%。这里需要将flex-grow重置为0,以防止项目宽度超过50%

如果将 flex-basis 设置为 100%,会怎么样?该项目单独占一行,其他项目将换行。

clipboard.png

flex 属性

flex属性是flex-grow, flex-shrinkflex-basis的简写,默认值为0 1 auto。后两个属性可选。这也说 flex 项目会根据其内容大小增长

flex 项目相对大小

.item {
    /* 默认值,相当于 flex:1 1 auto */
    flex: auto;
}

flex 项目的大小取决于内容。因此,内容越多的flex项目就会越大。

clipboard.png

flex 项目绝对大小

相反,当flex-basis属性设置为0时,所有flex项目大小会保持一致。

.item {
    /* 相当于  flex: 1 1 0% */
    flex: 1;
}

clipboard.png

我喜欢 flex 属性的几个点!

顾名思义,此属性可以灵活使用其值。 请看下面的例子。

一个值的情况

.item {
    flex: 1;
}

上面默认对应的值是 1 1 0,也就是 flex-grow: 1,flex-shrink:1, flex-basic: 0

两个值的情况

.item {
    flex: 1 1;
}

上面对应的值是 1 1 0,也就是 flex-grow: 1,flex-shrink:1, flex-basic: 0

一个长度值

如果 flex 值是一个长度值,这会作用于flex-basisflex-growflex-shrink默认为1

.item {
    flex: 100px;
    /* flex: 1 1 100px */
}

使用无单位0

有时,你想把 felx-basis 设置为 0,你可能会这样写:

.item {
    flex: 0;
}

不建议这样做,因为让开发人员和浏览器感到困惑。 你到底是要把 flex-grow 或者 flex-shirnk 设置为 0,还是将 flex-basis 设置为 0

所以,你应该添加一个单位,如px%

.item {
    flex: 0%;
    /* flex: 1 1 0% */
}

建议使用 flex 简写属性

当你需要设置growshrinkbasis时,最好使用flex属性来实现这个目的。

根据 CSS 规范:

建议开发者使用 `flex` 简写来控制灵活性,而不是直接使用它的普通属性,因为简写的可以正确地重置任何未指定的组件以适应常见情景。

flex 用例

用户头像

clipboard.png

flexbox 的一个常见用例是用户组件,头像和文本内容应该在同一行。

<div class="user">
    <img class="user__avatar" data-original="shadeed.jpg" alt="" />
    <div>
        <h3>Ahmad Shadeed</h3>
        <p>Author of Debugging CSS</p>
    </div>
</div>
.user {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.user__avatar {
    flex: 0 0 70px;
    width: 70px;
    height: 70px;
}

上面为 头像 添加了 flex:0 0 70px。 如果这里不这样设置,在某些旧版浏览器,图像看起来像被压缩的一样。 除此之外,flex 的优先级高于width属性(flex-direction: row)或height(flex-direction: column)。

如果我们仅通过调整flex属性来改变头像的大小,那么width将被浏览器忽略。

.user__avatar {
    /* width 是 100px, 不是 70px */
    flex: 0 0 100px;
    width: 70px;
    height: 70px;
}

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

头部

clipboard.png

如果想让一个标题填满所有可用的空间,使用flex: 1非常适合这种情况。

.page-header {
    display: flex;
    flex-wrap: wrap;    
}

.page-header__title {
    flex: 1;
}

输入框

clipboard.png

form {
    display: flex;
    flex-wrap: wrap;    
}

input {
    flex: 1;
    /* Other styles */
}

在两张卡片上对齐最后一项

clipboard.png

假设CSS grid具有两列布局。这里的问题是日期没有对齐,它们应该在同一条线上(红色那条)。

我们可以使用flexbox做到这一点。

<div class="card">
    <img data-original="thumb.jpg" alt="">
    <h3 class="card__title">Title short</h3>
    <time class="card__date"></time>
</div>

通过设置flex-direction: column,我们可以在标题上使用flex-grow使其填充可用空间,这样,即使标题很短也将日期保留在末尾。

.card {
    display: flex;
    flex-direction: column;
}

/* 第一个解决方案 */
.card__title {
    flex-grow: 1;
}

同样,无需使用flex-grow也可实现,我们使用margin-top: auto

/* 第二个解决方案*/
.card__date {
    margin-top: auto;
}

用例 - 多个 flex 属性

这里的意思是使用flex-growflex-shrink,但值不为1。在本节中,我们会探讨一些可以将其合并的想法。

footer

clipboard.png

像上面这样的布局, 我们可以这样写:

.actions {
    display: flex;
    flex-wrap: wrap;
}

.actions__item {
    flex: 2;
}

.actions__item.user {
    flex: 1;
}

扩展动画

clipboard.png

我们可以做的一件有趣的事情是在悬停时为flex项目设置动画。 这很有用的,下面是一个简单的例子:

.palette {
    display: flex;
    flex-wrap: wrap;
}

.palette__item {
    flex: 1;
    transition: flex 0.3s ease-out;
}

.palette__item:hover {
    flex: 4;
}

增加的用户体验

图片描述

源码:https://codepen.io/shshaw/pen...

当内容大于其包装器时

clipboard.png

不久前,我收到一个读者的问题,他的问题如下。 如图所示,两个图像应保留在其包装的边界内。

.wrapper {
    display: flex;
}

.wrapper img {
    flex: 1;
}

这里,即使 使用了 flex: 1,图像仍然会溢出。 根据CSS规范:

默认情况下,flex 项目不会缩小到其最小内容大小(最长的单词或固定大小的元素的长度)以下。 要更改此设置,请设置min-widthmin-height属性。

上面情况,是由于图片太大,flexbox不会缩小图片。 要更改此行为,我们需要设置以下内容:

.wrapper img {
    flex: 1;
    min-width: 0;
}

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://ishadeed.com/article/...

交流

文章每周持续更新,可以微信搜索【大迁世界 】第一时间阅读,回复【福利】有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 30 收藏 21 评论 1

LuoZunhua1993 赞了文章 · 8月12日

【网页特效】12 个炫酷背景特效库

作者:lindelof
译者:前端小智
来源:github
点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

1.particles-bg

地址:https://github.com/lindelof/particles-bg

效果:

clipboard.png

clipboard.png

clipboard.png

2.particles-bg-vue

地址:https://github.com/lindelof/awesome-web-effect

这是一个基于VUE的粒子动画组件。

clipboard.png

clipboard.png

3.jquery.ripples

地址:https://github.com/sirxemic/jquery.ripples

jQuery Ripples 插件向HTML添加一层水元素将波纹光标与WebGL的互动。您可以使用这种效果,让你的静态CSS背景图像更多的互动。

clipboard.png

4.MorphingBackgroundShapes

地址:https://github.com/codrops/MorphingBackgroundShapes

这是一个很具装饰性的网站背景效果,当用户在滚动到某一页面后此背景的SVG图形将随着变形和移动。

5. SegmentEffect

地址:https://github.com/codrops/SegmentEffect

背景分割装饰特效。

6.jQuery.BgSwitcher

地址:https://github.com/rewish/jquery-bgswitcher

jQuery.BgSwitcher实现背景图像切换效果。

7.BackgroundScaleHoverEffect

地址:https://github.com/codrops/BackgroundScaleHoverEffect

使用 CSSclip paths 重现背景缩放悬停特效。

https://klxxcdn.oss-cn-hangzhou.aliyuncs.com/histudy/hrm/media/66/7116.gif

8.ImageGridMotionEffect

地址:https://github.com/codrops/ImageGridMotionEffect

为背景网格的图像提供运动悬停特效。

9.jquery.adaptive-backgrounds.js

地址:https://github.com/briangonzalez/jquery.adaptive-backgrounds.js

adaptive-background.js是一款jQuery插件,可以根据div,img标签里图片的边框颜色来动态调整父标签的背景颜色,有点类似iTunes的专辑详情的效果.

clipboard.png

10.fixed-background-effect

地址:https://codyhouse.co/demo/fixed-background-effect/index.html#0

整屏滚动背景悬浮效果。

11.jquery-warpdrive-plugin

地址:https://github.com/NiklasKnaack/jquery-warpdrive-plugin

query-warpdrive-plugin是一款可以制作基于HTML5 canvas的炫酷星空背景特效的jquery插件。这个星空背景特效可通过配置参数进行灵活的配置,可用鼠标进行互动。

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

12.RainEffect

使用WebGL在不同场景下的一些实验性降雨和水滴效应。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://github.com/lindelof/a...


交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

赞 47 收藏 37 评论 0

LuoZunhua1993 关注了专栏 · 8月12日

终身学习者

我要先坚持分享20年,大家来一起见证吧。

关注 40749

LuoZunhua1993 赞了文章 · 6月22日

还搞不懂闭包算我输(JS 示例)

闭包并不是 JavaScript 特有的,大部分高级语言都具有这一能力。

什么是闭包?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

这段是 MDN 上对闭包的定义,理解为:一个函数及其周围封闭词法环境中的引用构成闭包。可能这句话还是不好理解,看看示例:

function createAction() {
    var message = "封闭环境内的变量";
    
    return function() {
        console.log(message);
    }
}

const showMessage = createAction();
showMessage();    // output: 封闭环境内的变量

这个示例是一个典型的闭包,有这么几点需要注意:

  1. showMessagecreateAction 执行后从中返回出来的一个函数
  2. createAction 内部是一个封闭的词法环境,message 作为该封装环境内的变量,在外面是绝不可能直接访问。
  3. showMessagecreateAction 外部执行,但执行时却访问到其内部定义的局部变量 message(成功输出)。这是因为 showMessage 引用的函数(createAction 内部的匿名函数),在定义时,绑定了其所处词法环境(createAction 内部)中的引用(message 等)。
  4. 绑定了内部语法环境的匿名函数被 return 带到了 createAction 封闭环境之外使用,这才能形成闭包。如果是在 createAction 内部调用,不算是闭包。

好了,我相信 1, 2, 4 都好理解,但是要理解最重要的第 3 点可能有点困难 —— 困难之处在于,这不是程序员能决定的,而是由语言特性决定的。所以不要认为是“你”创建了闭包,因为闭包是语言特性,你只是利用了这一特性

如果语言不支持闭包,类似上面的代码,在执行 showMessage 时,就会找不到 message 变量。我特别想去找一个例子,但是很不幸,我所知道的高级语言,只要能在函数/方法内定义函数的,似乎都支持闭包。

把局部定义的函数“带”出去

前面我们提到了可以通过 return 把局部定义的函数带出去,除此之外有没有别的办法?

函数在这里已经成为“货”,和其他货(变量)没有区别。只要有办法把变量带出去,那就有办法把函数带出去。比如,使用一个“容器”对象:

function encase(aCase) {
    const dog = "狗狗";
    const cat = "猫猫";
    aCase.show = function () {
        console.log(dog, cat);
    };
}

const myCase = {};
encase(myCase);
myCase.show();      // output: 猫猫 狗狗

是不是受到了启发,有没有联想到什么?

模块和闭包

对了,就是 exports 和 module.exports。在 CJS (CommonJS) 定义的模块中,就可以通过 exports.something 逐一带货,也可以通过 module.exports = ... 打包带货,但不管怎么样,exports 就是带货的那一个,只是它有可能是原来安排的 exports 也可能是被换成了自己人的 exports

ESM (ECMAScript Module) 中使用了 importexport 语法,也只不过是换种方法带货出去而已,和 return 带货差不多,区别只在于 return 只能带一个(除非打包),export 可以带一堆。

还要补充的是,不管是 CJS 还是 ESM,模块都是一个封装环境,其中定义的东西只要不带出去,外面是访问不到的。这和网页脚本默认的全局环境不同,要注意区别。

如果用代码来表示,大概是定义模块的时候以为是这样:

const var1 = "我是一个顶层变量吧";
function maybeATopFunction() { }

结果在运行环境中,它其实是这样的(注意:仅示意):

// module factory
function createModule_18abk2(exports, module) {
    const var1 = "我是一个顶层变量吧";
    function maybeATopFunction() { }
}

// ... 遥远的生产线上,有这样的示意代码
const module = { exports: {} };
const m18abk2 = createModule_18abk2(module) ?? module;

// 想明白 createModule_18abk2 为什么会有一个随机后缀没?

还是那个函数吗?

扯远了,拉回来。思考一个问题:理论上来说,函数是一个静态代码块,那么多次调用外层函数返回出来的闭包函数,是同一个吗?

试试:

function create() {
    function closure() { }
    return closure;
}

const a = create();
const b = create();

console.log(a === b);   // false

如果觉得意外,那把 closure() 换种方式定义看会不会好理解一点:

function create() {
    closure = function() { }
    return closure;
}

如果还不能理解,再看这个:

function create() {
    const a = function () { };
    const b = function () { };
    console.log(a === b);   // false
}

能理解了不:每一次 function 都定义了一个新的函数。函数是新的,名字不重要 —— 你能叫小明,别人也能叫小明不是。

所以,总结一下:


闭包是由一个函数以及其定义时所在封闭环境内的各种资源(引用)构成,拿到的每一个闭包都是独一无二的,因为构成闭包的环境资源不同(不同的局部环境,定义了不同的局部变量,传入了不同的参数等)。

闭包,这回搞明白了!


边城客栈

请关注公众号边城客栈

看完了先别走,点个赞 ⇓ 啊,赞赏 ⇘ 就更好啦!

查看原文

赞 40 收藏 21 评论 1

LuoZunhua1993 关注了专栏 · 6月21日

边城客栈

全栈技术专栏

关注 3128

LuoZunhua1993 发布了文章 · 6月15日

Vue + ArcGIS API for JavaScript 构建前端GIS应用(三)

接着上篇,继续介绍如何编写一个组件、组件间如何通行,如何解决组件通讯的一些硬伤,最终如何结合arcgis js api创建一个vue项目

传送门:
Vue + ArcGIS API for JavaScript 构建前端GIS应用(一)
Vue + ArcGIS API for JavaScript 构建前端GIS应用(二)
Vue + ArcGIS API for JavaScript 构建前端GIS应用(三)

p.s.:
配合示例代码服用效果更佳:https://github.com/GlennLuo19...

如何编写一个组件?

示例代码:https://github.com/GlennLuo19...

如何去编写一个单文件组件呢?下面使用一个简单的案例进行说明。
image.png
该组件包括一个状态count用于计数,两个按钮分别增加与减少count。

数据与事件的声明与绑定

首先,我们要做的是组件视图层的编写,很简单的3个标签,1个div、2个button

  1. 数据声明:这时候就需要在模块中新建一个data选项,在里面注册一条状态count
  2. 数据绑定:使用双大括号的形式把数据绑定到标签上。
  3. 方法注册:在methods选项中进行方法的注册。在方法中,我们直接操作的是刚刚注册的count状态
  4. 绑定事件:使用v-on:指令或者简写使用@,进行事件绑定

这样逻辑就完成了。方法直接操作状态,状态一旦更新,vue随即响应,对dom进行更新。
除此之外还有component、props、computed选项,在这就不做介绍了。

生命周期钩子函数

下面介绍一下重要的概念,生命周期钩子函数。

仍然用刚刚的例子去看一下生命周期钩子怎么用:
现在组件使用了created、mounted、updated三个常用的生命周期钩子函数,他们分别在组件创建好时执行、在组件装在完毕后执行、在组件状态更新后执行。效果请看例子。

我们使用生命周期可以实现一些逻辑。比如组件与后台的通信获取初始化数据就可以在mounted函数里面进行。

组件间如何通信?

示例代码:https://github.com/GlennLuo19...
某一个独立的组件很难实现一个比较完整的业务逻辑,因此组件间的通信是不能避免的。

下面就用一个例子说明父子组件之间是如何互相传值的。
image.png
首先介绍一下例子代码的结构:

  1. 在App组件包含了一个子组件parentComponent;parentComponent中包含子组件childComponent。这里就是用到了vue的component选项,就是把子组件注册进来,并作为一个标签放到temlate里面。
  2. parentComponent与childComponent为父子组件关系,可以看一下页面。如果子组件想使用父组件的某个数据怎么实现呢? 首先父组件具有一个状态msg,然后把msg作为一个参数,传给子组件。
  3. 我们在子组件中使用props选项接受到父组件传的值。这值是可以直接作为状态绑定的。
  4. 演示:点击父组件
  5. 子组件向父组件如何传值?首先父组件声明一个方法:获取一个数据存到自己声明的一个状态childMsg里面,并且把这个方法注册到子组件上。
  6. 在子组件里面使用this.$emit方法就可以把自己的值作为参数,激发父组件挂载的事件。

两种方式是不同的: 父组件向子组件传值是主动式的;子向父是被动式的。

但是问题来了:兄弟组件如何通信?多层级组件之间怎么通信?
vue的生态系统可以帮助解决以这个问题。

Vue生态系统

前面说到过,vue是一个是渐进式框架,仅仅依靠上面介绍的vue核心库很难满足一个工程项目建设的需要,同时vue原生存在的问题如:组件间通信困难的问题难以解决。但是在vue生态系统的帮助下,让vue有能力创建一个工程应用。在这里只是对vue生态的内容进行简单的介绍,具体何时使用?怎么使用?会在后面的案例中进行演示。

Devtools:

浏览器插件,便于在开发中使用。

Vue Loader:

Webapck中的一个loader,用于转译.vue文件,让单页组件可用。如果是想用webpack手工进行vue工程项目配置的话,这个loader是必不可少的。

Vue Cli:

通过Vue Cli可以帮助快速开发一个vue工程项目。提供vue-cli可以交互式的、快速的搭建一个项目的脚手架。它是基于 webpack 构建,并带有合理的默认配置,减少配置时间,绕过webpack这个坑。一个丰富的官方插件集合,集成了前端生态中最好的工具。

Vue Router:

控制vue的路由。

Vuex:

状态管理机,构建全局状态和局部状态,解决组件间通信问题。

用Vue构建前端工程项目

开发环境:

node.js
npm or yarn

IDE:

VSCode、Webstorm
介绍一下适合vue前端开发的vscode插件

项目构建:

方案:使用webpack进行vue环境配置;这里就可以应用前面谈主任提到的现代前端工具链在webpack基础上进行项目的配置,但是配置过程繁杂需要丰富的经验,容易遇到坑,影响项目启动的进度。

简化方案:使用vue生态中的vue-cli构建项目(重点介绍,并进行演示,包括使用控制台指令进行创建和使用可视化界面vue ui进行创建);

集成GIS

示例代码:https://github.com/GlennLuo19...

如何与arcgis js api进行集成?

我们不能在用npm安装arcgis api后直接用import的形式引用api到项目里来,因为我们的项目是es模式的模块化规范,而arcgis api是基于Dojo框架下AMD模式建立的。所以,想要在我们的项目中使用arcgis就必须借助其他的工具,这个工具就是esri-loader

构建一个简单的WebGIS

综合案例:vue + vue-cli + vuex + vue router + esri-loader技术栈
image.png

这个例子实现了哪些内容:

  1. 使用前端路由vue router实现了模块的切换:首页和地图模块
  2. 地图模块中集成了arcgis api,使用arcgis api实现了添加图层、删除图层的功能。在这里划分了两个不同的组件map和mapControl。使用vue的dev tools可以查看模块的划分。
  3. map和mapControl是独立的两个组件,但是逻辑功能是有耦合的,需要进行组件件的通行。这里使用了vuex作为通信方案,实现了mapControl组件对map组件的控制。

项目的代码

先来看一下项目的文件结构:

  • dist文件为最终的产品文件夹
  • node_modules为项目的第三方js库
  • public文件夹主要存放的是html模版,最终的vue实例是被挂在在这个html上的
  • src存放的是源码
  • 下面以.开头的这些文件为各种插件的配置文件比如eslintrc为eslint的配置文件、babel.config.js是babel的配置文件、vue.config.js是vue-cli的配置文件
  • package.json是该项目的包管理配置文件

重点看一下源码部分

  • assets文件夹存放的是前端的静态资源如图片、字体等
  • components用于存放划分很细的组件。
  • views是存放vue中模块级别的组件,在我们这个项目中就包括了home模块和myMap模块,也就是刚刚大家看到通过路由切换的模块。
  • App.vue是最大的一个组件,最终他会在入口文件main.js中被挂到html模版上。
  • router.js是写vue router的相关内容
  • store.js写的是vuex的内容

从mainjs看起,这声明了一个vue实例,启用了初始化vuex和vue router,并把应用级组件app挂在html模版id是app的div上。

在app组件是一个应用的整体框架。这里有两个router-link标签,进行前端路由的跳转。router-view等于是一个占位符,表示的是某个路由对应的组件。

在router.js里面我们可以看到/对应的是Home组件;/map对应的是MyMap组件。懒加载。

当我们进行路由跳转时,就可以进行组件的切换。

Home组件我们就不介绍了;重点介绍一下myMap组件

myMap里面注册两个组件:map和mapControl

在map组件里面我们启用esri-loader,在钩子函数mounted也就是在组件装载成功后进行arcgis api map类和view类的初始化。

在最后我们使用了initMap以及initView这两个函数,这两个是vuex中定义的mutation。

现在我们看看一下vuex的store是如何编写的:

一个store最重要的就是state、mutation、actions这像个选项:

  1. state表示状态跟vue中的data类似,这里可以理解为应用级别的data
  2. mutation(转变)用来写一些改变状态的函数,要求是纯函数
  3. action(动作)用来写一些业务逻辑包括一些一步过程

该项目声明了3个状态:

  1. map:用于存放应用中的map实例
  2. view:用于存放应用中的view实例
  3. layercount:用于记录图层数量

mutation里面写个两个函数
initMap:也就是将在map组件里面实例化的map对state中的map进行初始化
initView:与map类似,就是初始化view

当这两个对象被vuex管理之后,应用里的所有组件都可以调用vuex中的mutation或者action方法加上自己的参数对地图对象进行操作了

这里在action中写了两个方法增加图层和删除所有图层。

回到mapControl组件,我们使用的mapActions方法把vuex中的两个actions引用到组件里面,分别挂在了两个按钮的单击事件上。还可以看到我们还将vuex中的layerCount状态绑定到了组件的标签上,它跟data一样可以响应式渲染。

总结一下:

webGIS一般是以地图为核心,我们可以将地图对象等交给vuex进行管理,对于地图的操作逻辑也可以分离出来写在vuex中。对于其他的一些应用级别的对象也是如此。

这样我们就可以避免了大部分的通信问题。

最后进行一点补充:
我们结合一些热门UI框架如element UI或者ant design可以帮我创建更好看的、交互性更好的webGIS应用。

查看原文

赞 6 收藏 4 评论 6

LuoZunhua1993 发布了文章 · 6月15日

Vue + ArcGIS API for JavaScript 构建前端GIS应用(二)

传送门:
Vue + ArcGIS API for JavaScript 构建前端GIS应用(一)
Vue + ArcGIS API for JavaScript 构建前端GIS应用(二)
Vue + ArcGIS API for JavaScript 构建前端GIS应用(三)

介绍主要围绕着:

  1. 什么是Vue?
  2. 怎么写Vue的组件?
  3. 如何构建Vue工程项目?
  4. Vue与GIS如何集成?
  5. 如何用Vue构建一个WebGIS项目?

这5个问题去展开。

首先会介绍一些知识点,最后会有一个综合案例,结合前文介绍的内容,利用vue的技术栈构建一个简单的WebGIS前端应用。

p.s.:
Vue + ArcGIS API for JavaScript 构建前端GIS应用(二)中主要是开发前端的一些背景介绍以及Vue的一些基本知识,主要介绍1、2两点
如果觉得简单可以直接看Vue + ArcGIS API for JavaScript 构建前端GIS应用(三)

背景知识

在正式全面介绍vue之前,首先介绍一下需要了解的两个重要的背景知识,我们在使用vue写前端中许多环节需要他们的支持。

ES6

ECMAScript 6简称ES6,他是浏览器脚本语言的标准,而我们熟悉的JavaScript就是这个标准的一个实现。由于JavaScript的影响力非常大,日常场合中,这两个词几乎是等价的。

ES6为脚本语言增加了很多新的特性,为编写大型项目提供了更好的支持。我觉得其中最重要的就是ES6规范了规范化了之前形式多样的js模块化方案,如CommonJS、AMD、CMD等。现在呢,只要使用import关键字就能把js包引用进来,还不需要担心引用顺序问题。

目前,在使用Vue中js脚本编写使用ES6已经成了主流趋势。在后面进行vue介绍时候大家随处可以见到他的影子。想要系统的学习或者当做工具书查阅的话,建议去看阮一峰的es6那本书,有电子版,直接在网页上浏览就行了。

Webpack

Webpack是一个现代的JS应用程序的静态模块打包器。它运行在node.js中,主要作用是对js源码进行转译与打包,支持不同js模块化模式,还支持不同样式语法(sass、less等)的转译。

目前浏览器对ES6语法规范的兼容性还没有达到100%,需要Webpack进行模块化打包、转译等,让我们既能使用ES6特性,又能解决浏览器兼容性问题。

并且Webpack为开发者提供了良好的开发环境的支持,如Webpack Server就允许用户进行js代码的热更新,用户在开发中改了代码浏览器就能及时更新,不需要刷新。

Vue中的单文件组件(.vue)同样也需要webpack的转译。
从开发到产品打包一整套流程都需要Webpack的支持。


Vue.js(2.x)介绍

官网给出了对于vue的一个定义:

"Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。"
也就是说vue是一个前端视图层的渐进式框架

怎么理解渐进式框架?

vue既可以作为一个普通的js库来使用,不作为项目的核心框架,解决一些具体问题,如表单提交等。同时,vue也可以作为整个应用项目的核心,在vue生态的帮助下可以构建一个完整的、复杂的单页应用(SPA)

vue框架的意义是什么?

  1. 随着现代web技术的发展,包括js的发展,浏览器的发展等,web前端的能力大大提高,很多之前需要服务器端完成的工作现在在前端就能搞定。因此js的体量就回越来越大,但是由于缺少组织形式js代码就回变得混乱难而且难以理解。
  2. 减少用户对于用户界面的操作,简化开发,让用户更多的专注于业务逻辑的开发,减少对html元素的操作。业务逻辑交给开发者,元素操作交给vue。

vue的特点是什么?

react的特色两大特点就是声明式渲染和组件化,vue跟react很类似,也具有这两大特色,同时他还具有丰富的元素操作指令的特点。

声明式渲染: 这里就是声明一个数据与视图的关系,形成一种绑定关系,当数据发生变化的时候,视图也随即会发生变化,这样我们就不需要进行繁杂的DOM操作。

我们来看官方提供的一个案例(声明式渲染)。这里声明了一个vue的实例,在data模块注册了一个状态变量message,并且把这个变量绑定在了html的标签上。这个时候呢,我们只需要改变message变量的内容,视图随即就会发生变化。这个过程中我们是没有进行dom操作的。

丰富的元素操作指令:
vue指令指的是写在html标签里面的以v-作为前缀的指令,他们表示vue提供的特殊特性。他们会在dom上应用特殊的相应行为。

  1. v-if 表示条件指令:当满足条件时候,该标签会显示,不满足条件时标签会隐藏。看这个例子,我们在vue实体的data项中声明了一个seen状态,并且把状态作为显示条件,当seen为true的时候,被挂接的标签显示;当seen为false的时候标签就会消失。
  2. v-for 表示循环指令:一般是在用于渲染列表、卡片、表格等的时候使用,可以将数组循环绑定在标签上。
  3. v-on 表示事件监听指令:用于监听事件的挂载。v-on的冒号后面写的是事件类型,等号后面写事件触发后执行的方法。
  4. v-model 表示双向绑定指令:将视图层与vue中的数据记进行双向绑定,当message这个状态发生变化时,视图层input的内容会发生改变,而在视图层中修改input内容的时候,message也会发生改变。

上面讲到的例子都是来源于vue的官网上的,如果大家有空的话可以去看一看,试一试。

组件化应用构建:
刚刚我们演示的例子都是单例vue对象,但是在实际项目中一个vue实例都是又若干个组件组成的,每个组件是完全独立存在的,它包含了可视化的要素、一定的功能逻辑并且带有样式。
Component Tree

这样有助于让开发更简明;可以有效的进行开发任务的分配;如果设计良好,可以高效的复用。

实际上,我们在编写vue工程项目的时候就是在写一个个组件。
允许使用小型、独立和通常可复用的组件构建大型应用,一般同单文件组件( .vue)结合。下图红框中的都代表一个个组件,每个组件都是一个.vue文件,并且为树形结构中节点间的关系,互相之间为父子、兄弟或者更为复杂的嵌套关系。
Image.png

单文件组件

顾名思义单文件组件意思是一个文件表示一个完整的组件。由template(视图层)、script(脚本)、style(样式)组成。
编写<template>创建组件的可视化要素(标签),写法基本与html相同;
在<script>中使用vue的特性,做数据绑定和业务逻辑的实现;
<style>中可以进行有作用域的样式设置,通过配置style中还支持sass、less等css预处理语言。

查看原文

赞 1 收藏 1 评论 0

LuoZunhua1993 赞了文章 · 2019-08-22

JavaScript模块化发展

模块化是我们日常开发都要用到的基本技能,使用简单且方便,但是很少人能说出来但是的原因及发展过程。现在通过对比不同时期的js的发展,将JavaScript模块化串联起来整理学习记忆。

如何理解模块化

面临的问题

技术的诞生是为了解决某个问题,模块化也是。在js模块化诞生之前,开发者面临很多问题:随着前端的发展,web技术日趋成熟,js功能越来越多,代码量也越来越大。之前一个项目通常各个页面公用一个js,但是js逐渐拆分,项目中引入的js越来越多:

<script data-original="zepto.js"></script>
<script data-original="jhash.js"></script>
<script data-original="fastClick.js"></script>
<script data-original="iScroll.js"></script>
<script data-original="underscore.js"></script>
<script data-original="handlebar.js"></script>
<script data-original="datacenter.js"></script>
<script data-original="util/wxbridge.js"></script>
<script data-original="util/login.js"></script>
<script data-original="util/base.js"></script>

当年我刚刚实习的时候,项目中的js就是类似这样,这样的js引入造成了问题:

  1. 全局变量污染:各个文件的变量都是挂载到window对象上,污染全局变量。
  2. 变量重名:不同文件中的变量如果重名,后面的会覆盖前面的,造成程序运行错误。
  3. 文件依赖顺序:多个文件之间存在依赖关系,需要保证一定加载顺序问题严重。

这些问题严重干扰开发,也是日常开发中经常遇到的问题。

什么是模块化

clipboard.png

我觉得用乐高积木来比喻模块化再好不过了。每个积木都是固定的颜色形状,想要组合积木必须使用积木凸起和凹陷的部分进行连接,最后多个积木累积成你想要的形状。

模块化其实是一种规范,一种约束,这种约束会大大提升开发效率。将每个js文件看作是一个模块,每个模块通过固定的方式引入,并且通过固定的方式向外暴露指定的内容。

按照js模块化的设想,一个个模块按照其依赖关系组合,最终插入到主程序中。

模块化解决方案

模块化这种规范提出之后,得到社区和广大开发者的响应,不同时间点有多种实现方式。我们举个例子:a.js

// a.js
var aStr = 'aa';
var aNum = cNum + 1;

b.js

// b.js
var bStr = aStr + ' bb';

c.js

// c.js
var cNum = 0;

index.js

// index.js
console.log(aNum, bStr);

四份文件,不同的依赖关系(a依赖c,b依赖a,index依赖a b)在没有模块化的时候我们需要页面中这样:

<script data-original="./c.js"></script>    
<script data-original="./a.js"></script>
<script data-original="./b.js"></script>
<script data-original="./index.js"></script>

严格保证加载顺序,否则报错。

1. 闭包与命名空间

这是最容易想到的也是最简便的解决方式,早在模块化概念提出之前很多人就已经使用闭包的方式来解决变量重名和污染问题。

这样每个js文件都是使用IIFE包裹的,各个js文件分别在不同的词法作用域中,相互隔离,最后通过闭包的方式暴露变量。每个闭包都是单独一个文件,每个文件仍然通过script标签的方式下载,标签的顺序就是模块的依赖关系。

上面的例子我们用该方法修改下写法:

a.js

// a.js
var a = (function(cNum){
   var aStr = 'aa';
   var aNum = cNum + 1; 
    
    return {
       aStr: aStr,
       aNum: aNum
    };
})(cNum);

b.js

// b.js
var bStr = (function(a){
   var bStr = a.aStr + ' bb';
    
   return bStr;
})(a);

c.js

// c.js
var cNum = (function(){
   var cNum = 0;
    
   return cNum;
})();

index.js

;(function(a, bStr){
    console.log(a.aNum, bStr);
})(a, bStr)

这种方法下仍然需要在入口处严格保证加载顺序:

<script data-original="./c.js"></script>    
<script data-original="./a.js"></script>
<script data-original="./b.js"></script>
<script data-original="./index.js"></script>

这种方式最简单有效,也是后续其他解决方案的基础。这样做的意义:

  1. 各个js文件之间避免了变量重名干扰,并且最少的暴露变量,避免全局污染。
  2. 模块外部不能轻易的修改闭包内部的变量,程序的稳定性增加。
  3. 模块与外部的连接通过IIFE传参,语义化更好,清晰地知道有哪些依赖。

不过各个模块的依赖关系仍然要通过加装script的顺序来保证。

2. 面向对象开发

一开始一些人在闭包的解决方案上做出了规范约束:每个js文件始终返回一个object,将内容作为object的属性。

比如上面的例子中b.js

// b.js
var b = (function(a){
   var bStr = a.aStr + ' bb';
    
   return {
       bStr: bStr
   };
})(a);

及时返回的是个值,也要用object包裹。后来很多人开始使用面向对象的方式开发插件:

;(function($){
    var LightBox = function(){
        // ...
    };
    
    LightBox.prototype = {
        // ....
    };
    
    window['LightBox'] = LightBox;
})($);

使用的时候:

var lightbox = new LightBox();

当年很多人都喜欢这样开发插件,并且认为能写出这种插件的水平至少不低。这种方法只是闭包方式的小改进,约束js文件返回必须是对象,对象其实就是一些个方法和属性的集合。这样的优点:

  1. 规范化输出,更加统一的便于相互依赖和引用。
  2. 使用‘类’的方式开发,便于后面的依赖进行扩展。

本质上这种方法只是对闭包方法的规范约束,并没有做什么根本改动。

3. YUI

早期雅虎出品的一个工具,模块化管理只是一部分,其还具有JS压缩、混淆、请求合并(合并资源需要server端配合)等性能优化的工具,说其是现有JS模块化的鼻祖一点都不过分。

// YUI - 编写模块
YUI.add('dom', function(Y) {
  Y.DOM = { ... }
})

// YUI - 使用模块
YUI().use('dom', function(Y) {
  Y.DOM.doSomeThing();
  // use some methods DOM attach to Y
})

// hello.js
YUI.add('hello', function(Y){
    Y.sayHello = function(msg){
        Y.DOM.set(el, 'innerHTML', 'Hello!');
    }
},'3.0.0',{
    requires:['dom']
})

// main.js
YUI().use('hello', function(Y){
    Y.sayHello("hey yui loader");
})

YUI的出现令人眼前一新,他提供了一种模块管理方式:通过YUI全局对象去管理不同模块,所有模块都只是对象上的不同属性,相当于是不同程序运行在操作系统上。YUI的核心实现就是闭包,不过好景不长,具有里程碑式意义的模块化工具诞生了。

4. CommonJs

2009年Nodejs发布,其中Commonjs是作为Node中模块化规范以及原生模块面世的。Node中提出的Commonjs规范具有以下特点:

  1. 原生Module对象,每个文件都是一个Module实例
  2. 文件内通过require对象引入指定模块
  3. 所有文件加载均是同步完成
  4. 通过module关键字暴露内容
  5. 每个模块加载一次之后就会被缓存
  6. 模块编译本质上是沙箱编译
  7. 由于使用了Node的api,只能在服务端环境上运行

基本上Commonjs发布之后,就成了Node里面标准的模块化管理工具。同时Node还推出了npm包管理工具,npm平台上的包均满足Commonjs规范,随着Node与npm的发展,Commonjs影响力也越来越大,并且促进了后面模块化工具的发展,具有里程碑意义的模块化工具。之前的例子我们这样改写:

a.js

// a.js
var c = require('./c');

module.exports = {
    aStr: 'aa',
    aNum: c.cNum + 1
};

b.js

// b.js
var a = require('./a');

exports.bStr = a.aStr + ' bb';

c.js

// c.js
exports.cNum = 0;

入口文件就是 index.js

var a = require('./a');
var b = require('./b');

console.log(a.aNum, b.bStr);

可以直观的看到,使用Commonjs管理模块,十分方便。Commonjs优点在于:

  1. 强大的查找模块功能,开发十分方便
  2. 标准化的输入输出,非常统一
  3. 每个文件引入自己的依赖,最终形成文件依赖树
  4. 模块缓存机制,提高编译效率
  5. 利用node实现文件同步读取
  6. 依靠注入变量的沙箱编译实现模块化

这里补充一点沙箱编译:require进来的js模块会被Module模块注入一些变量,使用立即执行函数编译,看起来就好像:

(function (exports, require, module, __filename, __dirname) {
    //原始文件内容
})();

看起来require和module好像是全局对象,其实只是闭包中的入参,并不是真正的全局对象。之前专门整理探究过 Node中的Module源码分析,也可以看看阮一峰老师的require()源码解读,或者廖雪峰老师的CommonJS规范

5. AMD和RequireJS

Commonjs的诞生给js模块化发展有了重要的启发,Commonjs非常受欢迎,但是局限性很明显:Commonjs基于Node原生api在服务端可以实现模块同步加载,但是仅仅局限于服务端,客户端如果同步加载依赖的话时间消耗非常大,所以需要一个在客户端上基于Commonjs但是对于加载模块做改进的方案,于是AMD规范诞生了。

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后(前置依赖),这个回调函数才会运行。

AMD规范

AMD与Commonjs一样都是js模块化规范,是一套抽象的约束,与2009年诞生。文档这里。该约束规定采用require语句加载模块,但是不同于CommonJS,它要求两个参数:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

require(['math'], function (math) {

    math.add(2, 3);

});

定义了一个文件,该文件依赖math模块,当math模块加载完毕之后执行回调函数,这里并没有暴露任何变量。不同于Commonjs,在定义模块的时候需要使用define函数定义:

define(id?, dependencies?, factory);

define方法与require类似,id是定义模块的名字,仍然会在所有依赖加载完毕之后执行factory。

RequireJs

RequireJs是js模块化的工具框架,是AMD规范的具体实现。但是有意思的是,RequireJs诞生之后,推广过程中产生的AMD规范。文档这里

RequireJs有两个最鲜明的特点:

  1. 依赖前置:动态创建<script>引入依赖,在<script>标签的onload事件监听文件加载完毕;一个模块的回调函数必须得等到所有依赖都加载完毕之后,才可执行,类似Promise.all。
  2. 配置文件:有一个main文件,配置不同模块的路径,以及shim不满足AMD规范的js文件。

还是上面那个例子:

配置文件main.js

requirejs.config({
    shim: {
        // ...
    },
    paths: {
        a: '/a.js',
        b: '/b.js',
        c: '/c.js',
        index: '/index.js'
    }
});

require(['index'], function(index){
    index();
});

a.js

define('a', ['c'], function(c){
    return {
        aStr: 'aa',
        aNum: c.cNum + 1
    }
});

b.js

define('b', ['a'], function(a){
    return {
        bStr = a.aStr + ' bb';
    }
});

c.js

define('c', function(){
    return {
        cNum: 0
    }
});

index.js

define('index', ['a', 'b'], function(a, b){
    return function(){
        console.log(a.aNum, b.bStr);
    }
});

页面中嵌入

<script data-original="/require.js" data-main="/main" async="async" defer></script>

RequireJs当年在国内非常受欢迎,主要是以下优点:

  1. 动态并行加载js,依赖前置,无需再考虑js加载顺序问题。
  2. 核心还是注入变量的沙箱编译,解决模块化问题。
  3. 规范化输入输出,使用起来方便。
  4. 对于不满足AMD规范的文件可以很好地兼容。

不过个人觉得RequireJs配置还是挺麻烦的,但是当年已经非常方便了。

6. CMD和SeaJs

CMD规范

同样是受到Commonjs的启发,国内(阿里)诞生了一个CMD(Common Module Definition)规范。该规范借鉴了Commonjs的规范与AMD规范,在两者基础上做了改进。

define(id?, dependencies?, factory);

与AMD相比非常类似,CMD规范(2011)具有以下特点:

  1. define定义模块,require加载模块,exports暴露变量。
  2. 不同于AMD的依赖前置,CMD推崇依赖就近(需要的时候再加载)
  3. 推崇api功能单一,一个模块干一件事。

SeaJs

SeaJs是CMD规范的实现,跟RequireJs类似,CMD也是SeaJs推广过程中诞生的规范。CMD借鉴了很多AMD和Commonjs优点,同样SeaJs也对AMD和Commonjs做出了很多兼容。

SeaJs核心特点:

  1. 需要配置模块对应的url。
  2. 入口文件执行之后,根据文件内的依赖关系整理出依赖树,然后通过插入<script>标签加载依赖。
  3. 依赖加载完毕之后,执行根factory。
  4. 在factory中遇到require,则去执行对应模块的factory,实现就近依赖。
  5. 类似Commonjs,对所有模块进行缓存(模块的url就是id)。
  6. 类似Commonjs,可以使用相对路径加载模块。
  7. 可以向RequireJs一样前置依赖,但是推崇就近依赖。
  8. exports和return都可以暴露变量。

修改下上面那个例子:

a.js

console.log('a1');
define(function(require,exports,module){
    console.log('inner a1');
    require('./c.js')
});
console.log('a2')

b.js

console.log('b1');
define(function(require,exports,module){
    console.log('inner b1');
});
console.log('b2')

c.js

console.log('c1');
define(function(require,exports,module){
    console.log('inner c1');
});
console.log('c2')

页面引入

<body>
    <script data-original="/sea.js"></script>
    <script>
    seajs.use(['./a.js','./b.js'],function(a,b){
        console.log('index1');
    })    
    </script>
</body>

对于seaJs中的就近依赖,有必要单独说一下。来看一下上面例子中的log顺序:

  1. seaJs执行入口文件,入口文件依赖a和b,a内部则依赖c。
  2. 依赖关系梳理完毕,开始动态script标签下载依赖,控制台输出:

    a1
    a2
    b1
    b2
    c1
    c2
  3. 依赖加载之后,按照依赖顺序开始解析模块内部的define:inner a1
  4. 在a模块中遇到了require('./c'),就近依赖这时候才去执行c模块的factory:inner c1
  5. 然后解析b模块:inner b1
  6. 全部依赖加载完毕,执行最后的factory:index

完整的顺序就是:

a1
a2
b1
b2
c1
c2
inner a1
inner c1 
inner b1
index

这是一个可以很好理解SeaJs的例子。

7. ES6中的模块化

之前的各种方法和框架,都出自于各个大公司或者社区,都是民间出台的结局方法。到了2015年,ES6规范中,终于将模块化纳入JavaScript标准,从此js模块化被官方扶正,也是未来js的标准。

之前那个例子再用ES6的方式实现一次:

a.js

import {cNum} from './c';

export default {
    aStr: 'aa',
    aNum: cNum + 1
};

b.js

import {aStr} from './a';

export const bStr = aStr + ' bb';

c.js

export const bNum = 0;

index.js

import {aNum} from './a';
import {bStr} from './b';

console.log(aNum, bStr);

可以看到,ES6中的模块化在Commonjs的基础上有所不同,增加了关键字import,export,default,as,from,而不是全局对象。另外深入理解的话,有两点主要的区别:

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

一个经典的例子:

// counter.js
exports.count = 0
setTimeout(function () {
  console.log('increase count to', ++exports.count, 'in counter.js after 500ms')
}, 500)

// commonjs.js
const {count} = require('./counter')
setTimeout(function () {
  console.log('read count after 1000ms in commonjs is', count)
}, 1000)

//es6.js
import {count} from './counter'
setTimeout(function () {
  console.log('read count after 1000ms in es6 is', count)
}, 1000)

分别运行 commonjs.js 和 es6.js:

➜  test node commonjs.js
increase count to 1 in counter.js after 500ms
read count after 1000ms in commonjs is 0
➜  test babel-node es6.js
increase count to 1 in counter.js after 500ms
read count after 1000ms in es6 is 1

这个例子解释了CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

更多ES6模块化特点,参照阮一峰老师的ECMAScript 6 入门

总结思考

写了这么多,其实都是蜻蜓点水地从使用方式和运行原理分析了不同方法的实现。现在重新看一下当时模块化的痛点:

  1. 全局变量污染:各个文件的变量都是挂载到window对象上,污染全局变量。
  2. 变量重名:不同文件中的变量如果重名,后面的会覆盖前面的,造成程序运行错误。
  3. 文件依赖顺序:多个文件之间存在依赖关系,需要保证一定加载顺序问题严重。

不同的模块化手段都在致力于解决这些问题。前两个问题其实很好解决,使用闭包配合立即执行函数,高级一点使用沙箱编译,缓存输出等等。

我觉得真正的难点在于依赖关系梳理以及加载。Commonjs在服务端使用fs可以接近同步的读取文件,但是在浏览器中,不管是RequireJs还是SeaJs,都是使用动态创建script标签方式加载,依赖全部加载完毕之后执行,省去了开发手动书写加载顺序这一烦恼。

到了ES6,官方出台设定标准,不在需要出框架或者hack的方式解决该问题,该项已经作为标准要求各浏览器实现,虽然现在浏览器全部实现该标准尚无时日,但是肯定是未来趋势。

参考

  1. JavaScript模块化开发的演进历程
  2. 精读 js 模块化发展
  3. 浅谈模块化开发
  4. 深入理解 ES6 模块机制
  5. Module 的加载实现
  6. SeaJS 从入门到原理
查看原文

赞 21 收藏 12 评论 6

LuoZunhua1993 关注了用户 · 2019-07-08

闪电矿工翻译组 @icepy

关注 18

LuoZunhua1993 赞了文章 · 2019-07-08

【译】来用 SVG 和 CSS 画朵云彩吧

译文出自:闪电矿工翻译组

原文地址:Drawing Realistic Clouds with SVG and CSS

原文作者:Beau Jackson

仓库原文链接:Drawing Realistic Clouds with SVG and CSS

译者:sichenguo

【译】来用 SVG 和 CSS 画朵云彩吧

希腊神话中有这样一个故事是讲述宙斯创造出来一个云女神涅斐勒,并且类似大多数的希腊神话一样的,这个故事非常的奇异且限制级。下面一个简短克制的版本。

我们能够知道的是: 涅斐勒是由宙斯以他自己美丽的妻子的形象创造的。一个凡人遇见涅斐勒,陷入爱河,并且他们一起有了一个孩子,确切的说是一个半人半马的婴儿。

很怪诞对吧,值得庆幸的是,在浏览器中创建云的过程要简单得多,而且风险要小得多。
image

(Demo)

最近,我发现开发者Yuan Chuan 已经实现了用代码生成逼真的云。对我来说,浏览器中的云这个概念一直如同希腊神话中的那边神秘。

让我们来看一下这个’画笔‘吧 (点这里),可以见到的是作者通过使用 box-shadow 作为包含两个滤镜的 <filter> 元素的补充实现了这个令人惊叹的‘云图’!

想要绘制出兼顾写实和精致的云图,需要搭配使用feTurbulencefeDisplacementMap 这两个滤镜。这两个滤镜不仅可以具有强大且复杂的功能,并且还可以提供一些令人兴奋的特性(其中包含奥斯卡获奖算法))!当然,这些功能在浏览器‘引擎盖’下有着令人生畏的复杂性。

虽然这些 SVG 滤镜的物理特性超出了本文的范围,但 MDNw3.org 上提供了大量文档供学习参考。另外还有 feTurbulencefeDisplacement 介绍

对于本文,我们将专注于学习使用这些 SVG 滤镜来实现令人惊奇的效果。滤镜背后的实现算法并不在我们的研究范围内,就像艺术家虽然可以绘制出美丽的景观但却不用懂得油漆的分子结构。

image

而我们需要做的只是密切关注一小部分 SVG 属性,这些属性使得我们可以在浏览器中绘制逼真的云图。通过学习这些属性可以让我们在项目中按照自己的意愿更好的制作出特定的滤镜效果。

一些必要的前置基础知识

CSS 规则 box-shadow的五个值得关注的属性:

box-shadow: <offsetX> <offsetY> <blurRadius> <spreadRadius> <color>;

让我们来增大这些值(可能会高于正常的开发者会做的),这样在视图的右下方就会有阴影出现。 !image(Demo)

#cloud-square {
    background: turquoise;
    box-shadow: 200px 200px 50px 0px #000;
    width: 180px;
    height: 180px;
}

#cloud-circle {
    background: coral;
    border-radius: 50%;
    box-shadow: 200px 200px 50px 0px #000;
    width: 180px;
    height: 180px;
}

你肯定表演或者见过过皮影戏吧?
imageCredit: Double-M

类似于通过改变手的形状来改变阴影的方式,我们的 HTML 中的“源形状”可以通过移动或者变形来同步影响其在浏览器中呈现的阴影的形状。box-shadow 复制原始图形的圆角效果。SVG 滤镜对于元素都以及元素的 shadow 的都会生效。

<svg width="0" height="0">
    <filter id="filter">
        <feTurbulence type="fractalNoise" baseFrequency=".01" numOctaves="10" />
        <feDisplacementMap in="SourceGraphic" scale="10" />
    </filter>
</svg>

到目前为止,上面的 SVG 标签不会被渲染出来的,因为我们没有定义任何视觉样式(更不用说零宽度、高度)。它的唯一目的是保持我们喂养的过滤器 SourceGraphic(也就是我们的<div>)。我们的源<div> 和它的阴影都被滤波器独立地扭曲。

我们通过 ID 选择器(#cloud-circle) 将下面的 CSS 规则添加到目标元素上:

#cloud-circle {
    filter: url(#filter);
    box-shadow: 200px 200px 50px 0px #fff;
}

看这里!

好吧,添加上 SVG 滤镜依旧没能给我们带来什么惊喜。

image
(Demo)

别担心!这才仅仅只是开始,更有趣的还在后面。

测试 feDisplacementMap scale 属性

使用这一属性进行的一些实验可以产生显著的效果。暂时,让我们保持它的 feTurbulence 不变,只调整 DisplacementMapscale 属性。

随着 scale 的增加(每次增加 30 ),我们的源 <div> 开始扭曲变形且它的投下阴影更接近天空中云随机的形状。

<feDisplacementMap in="SourceGraphic" scale="180" />

image
The scale attribute incremented by values of 30. (Demo)

好的,通过改变 scale ,好像终于接近了正确云形状的的数值范围!让我们稍微改变下颜色,以便看起来更像一朵 云彩 ☁️。

body {
    background: linear-gradient(165deg, #527785 0%, #7fb4c7 100%);
}

#cloud-circle {
    width: 180px;
    height: 180px;
    background: #000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 200px 200px 50px 0px #fff;
}

现在我们的图形越来越接近真实的云效果了!

image

调整 box-shadow 的值

下面的一组图像显示了 blur 对 box-shadow 效果的影响。下面的图形中 blur 的数组依次递增。

image
The cloud becomes "softer" as the blur value increases.

为了使我们的云带有一些积云效果,可以稍微扩大 <div> 的尺寸。

#cloud-circle {
    width: 500px;
    height: 275px;
    background: #000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 200px 200px 60px 0px #fff;
}

image好的,但是现在的源 div 元素正在成为障碍。😫

等等!我们已经扩大了源元素的尺寸,但是它现在已经开始影响我们的“云(白色阴影)”了。 很简单,只需要相距更远的地方投影遍可以解决这个遮挡问题。

而通过通过一些 CSS 定位很好地实现源元素的隐层。<body> 元素是云 div 元素的父元素,默认情况下是静态定位的。 将苏设置为就绝对定位就可以将不需要展示的 #cloud-circle 隐藏。

#cloud-circle {
    width: 500px;
    height: 275px;
    background: #000;
    border-radius: 50%;
    filter: url(#filter);
    box-shadow: 400px 400px 60px 0px #fff; /* Increase shadow offset */
    position: absolute; /* Take the parent out of the document flow */
    top: -320px; /* Move a little down */
    left: -320px; /* Move a little right */
}

很棒,这样看起来我们的云图已经初步成型了。

codepen
image

平摊在屏幕之上的云彩,嗯,这应该是一个对我们绘制出的云图一个很好的描述,总是感觉还缺少点什么,我们应该还可以做的会更好的!

用层来表达深度

这就是我们想要的:

A photo of clouds against a blue sky. The clouds have shades of gray that provide depth.Credit: 图源:pcdazero

从这张照片中云的深度,质感和丰富程度来看,有一件事是清楚的:宙斯是上过艺术学校。至少,他肯定是阅读过通用组件设计原则的,这个原则阐述了一个通用性的概念:

[...]照明偏差在深度和自然的解释中起着重要作用,并且可以由设计师以各种方式操纵......使用明暗区域之间的对比度使得外观具有景深。

这段话为我们提供了一个对云图进行优化更加真实的提示。通过将不同形状,大小和颜色的图层堆叠在一起,我们可以在参考图像制造对比度中以来渲染云图。需要的只是使用 filter 制造多个图层。

image

<svg width="0" height="0">
    <!-- Back Layer -->
    <filter id="filter-back">
        <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012"
            numOctaves="4"
        />
        <feDisplacementMap in="SourceGraphic" scale="170" />
    </filter>
    <!-- Middle Layer -->
    <filter id="filter-mid">
        <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012"
            numOctaves="2"
        />
        <feDisplacementMap in="SourceGraphic" scale="150" />
    </filter>
    <!-- Front Layer -->
    <filter id="filter-front">
        <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012"
            numOctaves="2"
        />
        <feDisplacementMap in="SourceGraphic" scale="100" />
    </filter>
</svg>

再加上图层可以提供更多的角度去探索 feTurbulence 以及 实现更多的功能。 我们选择使用更加平滑的 type :fractalNoise,然后将 numOctaves 设置为 6。

<feTurbulence type="fractalNoise" baseFrequency="n" numOctaves="6" />

现在,让我们将注意力集中在 baseFrequency 属性上。以下是我们在逐渐增加 baseFrequency 值的时得到的结果 :

image值取两端的值时,图形都会趋向于圆形。区别就是越小,更模糊。值越大,图形就更加显得生硬。

turbulence,噪音,频率和 octave 这样的词可能看起来很奇怪甚至令人困惑。但不要害怕!将滤镜的效果类比为声波实际上非常准确。我们可以将低频率(baseFrequency=0.001)等同于低噪声和低频率,更高的 (baseFrequency=0.1) 值则与更清晰的音调相对应。

我们可以看到,我们对积云效果的对应的 baseFrequency 值大概位于 0.005~0.01 范围之间。

使用 numOctaves 添加细节

增大 numOctaves 的值可以以极其细致的细节渲染图像。但是这需要大量计算,因此需要注意的是:更细致的渲染也可能带来的还有性能问题,所以如非必要,请将此值尽量控制在一定范围内。

imageThe higher the value we put into numOctaves the more granular detail give to our cloud.

好消息就是,在此例中,我们的云图需要的细节并不需要使用过高的 numOctaves值,如上图所示,numOctaves 为 4 或者 5 就可以得到不错的效果。

效果图

codepen
image

改变 seed 属性值可以产生多样的云图

至于 seed 属性还可以再深入研究一下。但是,就我们的目的而言, seed 的作用可以对形状造成影响。

Perlin Noise 函数(上文提到)会使用此值作为其随机数生成器的初始值。如果不设置该属性将默认 seed 为零。跟 baseFrequency 的区别就是,无论我们给出什么价值 seed,都不需要担心性能损失。

image

不同的 seed 值产生不同的形状

上面的 GIF 展示了不同 seed 值时的效果。需要注意的是,每朵云都是分层的复合云。(虽然我调整了每个图层的属性,但我保持各自的 seed 值分布均匀。)

image
Credit: Brockenhexe

在这里,仔细观察参考图像,我将 3 个‘云’层(不透明度不同)叠加到一个 div 上。通过反复试验和调整 seed 参数值,最终得到的下面这个类似于照片中云的形状的效果图。

codepen

image

天空才是真正的极限

当然,如果我们认为我们绘制的这个云彩要比宙斯创造的女神还要漂亮,肯定就是狂妄自大了。

但是,随着我们对 CSSSVG 滤镜的更多的了解,我们就越有能力创造出在视觉上令人惊叹的图形,比如下面这些:

Reflecting Mist

imageAnimated Reflecting mist

Alto-Cirrus Clouds

image

在这篇文章中我们沉浸在 SVG 强大功能和复杂性的海洋中,虽然 SVG 滤镜通常看起来比较复杂且难以理解。

然而就好像A Single Div project这个项目和Diana Smith's painting techniques这个例子一样,有趣的和有创意的方法总是可以得到令人惊艳的效果。

我相信很多开发者都有能力制作出一个云彩出来,但是一个可以轻松制作出云彩的工具会更受欢迎。因此我开发了一个小工具做这件事。

查看原文

赞 19 收藏 14 评论 1

认证与成就

  • 获得 21 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-08-08
个人主页被 555 人浏览