迷鹿

迷鹿 查看完整档案

苏州编辑中国矿业大学  |  软件工程 编辑中国制造  |  Target 编辑 www.google.com 编辑
编辑

-

一个贪多却幻想着嚼得烂的后端开发者

个人动态

迷鹿 提出了问题 · 4月23日

怎样在mac的shell脚本中进入到其他目录

我想在mac电脑中写一个shell脚本。姑且命名为 test.sh
这个脚本存放在 文稿(/Users/lb/Documents) 文件夹里。
现在有个问题困扰了我很久,我想在这个test.sh中,进入到该脚本所在的目录,即 文稿(/Users/lb/Documents) 文件夹。
请问我该怎样实现呢?
我尝试了很多种方案,包括但不限于 cd "$( dirname "${BASH\_SOURCE\[0\]}" )"cd "dirname $0"

相对目录呦,我希望这个脚本可移植,所以需要使用相对路径的方式,进入到该脚本的所在目录

我当前的脚本如下(mac不在身边,所以在 windows 上写了个样例)
image.png


image.png

关注 2 回答 1

迷鹿 赞了文章 · 1月7日

使用vue实现一个电子签名组件

使用vue实现一个电子签名组件

在生活中我们使用到电子签名最多的地方可能就是银行了,每次都会让你留下大名。今天我们就要用vue实现一个电子签名的面板

想要绘制图形,第一步想到的就是使用canvas标签,在之前的文章里我们使用canvas实现了一个前端生成图形验证码的组件,被吐槽不够安全,那么这个电子签名组件想必不会被吐槽了吧~

canvas

<canvas> 标签是 HTML 5 中的新标签。
<canvas> 标签只是图形容器,您必须使用脚本来绘制图形。

canvas标签本身是没有绘图能力的,所有的绘制工作必须在 JavaScript 内部完成。

使用canvas绘图有几个必要的步骤:

  1. 获取canvas元素
  2. 通过canvas元素创建context对象
  3. 通过context对象来绘制图形

在当前电子签名需求中,由于签名其实是由一条条线组成的,因此我们会用到以下几个方法:

  1. beginPath() :开始一条路径或重置当前的路径
  2. moveTo():把路径移动到画布中的指定点,不创建线条
  3. lineTo():添加一个新点,然后在画布中创建从该点到最后指定点的线条
  4. stroke():绘制已定义的路径
  5. closePath():创建从当前点回到起始点的路径

事件

想要在canvas中绘图,还需要绑定几个特定的事件,而这些事件在pc端和手机端不尽相同

pc端事件

  • mousedown
  • mousemove
  • mouseup

手机端事件

  • touchstart
  • touchmove
  • touchend

核心代码

初始化canvas标签并绑定事件

<canvas
        @touchstart="touchStart"
        @touchmove="touchMove"
        @touchend="touchEnd"
        ref="canvasF"
        @mousedown="mouseDown"
        @mousemove="mouseMove"
        @mouseup="mouseUp"
      ></canvas>

获取画笔

mounted生命周期初始化

mounted() {
    let canvas = this.$refs.canvasF;
    canvas.height = this.$refs.canvasHW.offsetHeight - 100;
    canvas.width = this.$refs.canvasHW.offsetWidth - 10;
    this.canvasTxt = canvas.getContext("2d");
    this.canvasTxt.strokeStyle = this.color;
    this.canvasTxt.lineWidth = this.linewidth;
  }

事件处理

mouseDown

//电脑设备事件
    mouseDown(ev) {
      ev = ev || event;
      ev.preventDefault();

      let obj = {
        x: ev.offsetX,
        y: ev.offsetY
      };
      this.startX = obj.x;
      this.startY = obj.y;
      this.canvasTxt.beginPath();//开始作画
      this.points.push(obj);//记录点
      this.isDown = true;
    },

touchStart

//移动设备事件
    touchStart(ev) {
      ev = ev || event;
      ev.preventDefault();
      if (ev.touches.length == 1) {
        this.isDraw = true; //签名标记
        let obj = {
          x: ev.targetTouches[0].clientX,
          y:
            ev.targetTouches[0].clientY -
            (document.body.offsetHeight * 0.5 +
              this.$refs.canvasHW.offsetHeight * 0.1)
        }; //y的计算值中:document.body.offsetHeight*0.5代表的是除了整个画板signatureBox剩余的高,this.$refs.canvasHW.offsetHeight*0.1是画板中标题的高
        this.startX = obj.x;
        this.startY = obj.y;
        this.canvasTxt.beginPath();//开始作画
        this.points.push(obj);//记录点
      }
    },

mouseMove

//电脑设备事件
    mouseMove(ev) {
      ev = ev || event;
      ev.preventDefault();
      if (this.isDown) {
        let obj = {
          x: ev.offsetX,
          y: ev.offsetY
        };
        this.moveY = obj.y;
        this.moveX = obj.x;
        this.canvasTxt.moveTo(this.startX, this.startY);//移动画笔
        this.canvasTxt.lineTo(obj.x, obj.y);//创建线条
        this.canvasTxt.stroke();//画线
        this.startY = obj.y;
        this.startX = obj.x;
        this.points.push(obj);//记录点
      }
    },

touchMove

//移动设备事件
    touchMove(ev) {
      ev = ev || event;
      ev.preventDefault();
      if (ev.touches.length == 1) {
        let obj = {
          x: ev.targetTouches[0].clientX,
          y:
            ev.targetTouches[0].clientY -
            (document.body.offsetHeight * 0.5 +
              this.$refs.canvasHW.offsetHeight * 0.1)
        };
        this.moveY = obj.y;
        this.moveX = obj.x;
        this.canvasTxt.moveTo(this.startX, this.startY);//移动画笔
        this.canvasTxt.lineTo(obj.x, obj.y);//创建线条
        this.canvasTxt.stroke();//画线
        this.startY = obj.y;
        this.startX = obj.x;
        this.points.push(obj);//记录点
      }
    },

mouseUp

//电脑设备事件
    mouseUp(ev) {
      ev = ev || event;
      ev.preventDefault();
      if (1) {
        let obj = {
          x: ev.offsetX,
          y: ev.offsetY
        };
        this.canvasTxt.closePath();//收笔
        this.points.push(obj);//记录点
        this.points.push({ x: -1, y: -1 });
        this.isDown = false;
      }
    },

touchEnd

//移动设备事件
    touchEnd(ev) {
      ev = ev || event;
      ev.preventDefault();
      if (ev.touches.length == 1) {
        let obj = {
          x: ev.targetTouches[0].clientX,
          y:
            ev.targetTouches[0].clientY -
            (document.body.offsetHeight * 0.5 +
              this.$refs.canvasHW.offsetHeight * 0.1)
        };
        this.canvasTxt.closePath();//收笔
        this.points.push(obj);//记录点
        this.points.push({ x: -1, y: -1 });//记录点
      }
    },

重写

发现自己写错字了,擦掉画板重新写过

//重写
    overwrite() {
      this.canvasTxt.clearRect(
        0,
        0,
        this.$refs.canvasF.width,
        this.$refs.canvasF.height
      );
      this.points = [];
      this.isDraw = false; //签名标记
    },

用到的data

data() {
    return {
      points: [],
      canvasTxt: null,
      startX: 0,
      startY: 0,
      moveY: 0,
      moveX: 0,
      endY: 0,
      endX: 0,
      w: null,
      h: null,
      isDown: false,
      color: "#000",
      linewidth: 3,
      isDraw: false //签名标记
    };
  },

file
转评赞就是最大的鼓励

查看原文

赞 123 收藏 82 评论 19

迷鹿 赞了回答 · 2019-11-13

“请在微信客户端打开链接”的实现原理?

最简单的方案就是利用 http request header 里的 User-Agent,它是端的一个标识,微信的 ua 里包含 MicroMessenger 这么一个字符串,可以用正则做匹配

其次,确实可以伪造,但是这里的提示更重要的是提示那些正常的用户,你的链接应该在微信内打开才能正常使用,如果你是伪造的,后续的流程因为不是在微信内所以也无法走通,也没有任何影响

关注 6 回答 5

迷鹿 赞了回答 · 2019-11-13

“请在微信客户端打开链接”的实现原理?

实际是后台判断的是浏览器的UA 是不是 包含 “MicroMessenger” (是否为微信浏览器)

关注 6 回答 5

迷鹿 赞了回答 · 2019-09-26

C#多线程开发

.NET 中的并行处理、并发和异步编程 https://docs.microsoft.com/zh...

关注 4 回答 2

迷鹿 收藏了文章 · 2019-09-16

不要肆无忌惮地在你的项目中使用 ES78910 了~

图片描述

如果我有故事,你有 star 吗~

故事背景

在一次 code review 中,我在我们的项目(项目基于 vue-cli 3 创建)中找到了这句代码 MDN

[1, 2, [3, 4, [5, 6]]].flat(Infinity); // [1, 2, 3, 4, 5, 6]

嗯嗯~多维数组扁平化,很酷炫霸拽吊炸天~

我再一看兼容性..

图片

打扰了..

先脑补一波互怼的画面

我 : 老哥,你这个 API 是 ES2019 新特性啊,万万使不得啊~

图片

同事: 我有 vue-cli 3 啊~ 他封装好了 Babel 啊, 我大 vue-cli 3 天下无敌啊~

我 : 我...我真想跳起来打他的膝盖啊~ 凭一句话 好像是毫无说服力啊,是时候表演真正的技术了..

说(睡)服同事

core-js

Modular standard library for JavaScript. Includes polyfills for ECMAScript up to 2019: promises, symbols, collections, iterators, typed arrays, many other features, ECMAScript proposals, some cross-platform WHATWG / W3C features and proposals like URL. You can load only required features or use it without global namespace pollution.

core-js 是 babel 转码的核心包,它使用 es5 API实现了一些 ECMAScript 到 2019 年的 polyfills,并且提供按需加载,且使用它不污染全局名称空间。

@babel/preset-env

@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!

@babel/preset-env 是一个智能插件集合,允许您使用最新的 JavaScript API,而不需要对目标环境所需的API转换(以及可选的 browser polyfills)进行微管理。这不仅使您的生活更轻松,而且 JavaScript 包也更小!

下面我们简单了解一下它的其中两个核心配置项.

useBuiltIns

"usage" | "entry" | false, defaults to false.

(提供"usage" | "entry" | false 三个配置项,默认值为 false)

This option configures how @babel/preset-env handles polyfills.

(这个配置项用来决定@babel/preset-env 如何处理 polyfills)

When either the usage or entry options are used, @babel-preset-env will add direct references to core-js modules as bare imports (or requires). This means core-js will be resolved relative to the file itself and needs to be accessible.

(当使用 usage 或 entry 配置项时,@babel-preset-env 将直接(entry)引用(或按需(usage)引入)core-js 模块,这意味着 core-js 将对文件本身进行解析)

Since @babel/polyfill was deprecated in 7.4.0, we recommend directly adding core-js and setting the version via the corejs option.

(由于@babel/polyfill 在 7.4.0 中被弃用,我们建议直接添加 core-js 并通过 corejs 选项设置版本。)

corejs

2, 3 or { version: 2 | 3, proposals: boolean }, defaults to 2.

(指定 corejs 版本,2 或 3,默认值为 2)

This option only has an effect when used alongside useBuiltIns: usage or useBuiltIns: entry, and ensures @babel/preset-env injects the correct imports for your core-js version.

(此选项只有在 useBuiltIns 选项配置为 entry 或 usage 时才生效,并确保@babel/preset-env 为您的 core-js 版本注入正确的引入)

Ok,接下来我们来看一哈 Vue-cli 3 的 babel 配置~

// babel.config.js
module.exports = {
  presets: ['@vue/app']
};

可以看到 vue-cli 3 这边用的预设集合是自己封装的@vue/app,我们在 node_modules 找到@vue/app 的 package.json

图片

// package.json

"dependencies":{

  "@babel/preset-env": "^7.0.0 < 7.4.0",

  "core-js": "^2.6.5"
}

可以看到依赖里有 core-js 2.x 版本和@babel/preset-env ~

打开@vue/app 的 index.js

//index.js  部分代码

const envOptions = {
  spec,
  loose,
  debug,
  modules,
  targets,
  useBuiltIns, //  划重点,此处值 已定义为 'usage'
  ignoreBrowserslistConfig,
  configPath,
  include,
  exclude: polyfills.concat(exclude || []),
  shippedProposals,
  forceAllTransforms
};

presets.unshift([require('@babel/preset-env'), envOptions]);

由上,我们可以得出结论,vue-cli 使用的 vue-preset-app 封装了@babel/preset-env`,且配置

useBuiltIns: 'usage';

corejs 没做配置,所以为默认值 2

useBuiltIns: 'usage';
corejs: 2;

这么一看,结合我们上面所讲知识,flat 是应该会被转成 es5 咯 ? 啪啪啪,打脸?

倔强的我上 github 打开了 core-js

图片

奇怪的是,我在 core-js 2.65 版本里并没有找到 flat API的实现.

图片

求知欲爆炸的我,翻了 core-js@3 的文档,找到了以下这段话

图片

发现 Array.prototype.flat API是在 core-js@3 才加入的。

图片

结论

vue-cli 3 使用的是 core-js2.x 版本,所以并不能转义 Arrary.prototype.flat 这个API。

实践

得出理论 不实践一波 好像不符合我的风格啊~

npm init -y

npm i @babel/core @babel/preset-env -D
const babel = require('@babel/core');

const code = `[1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);`;
const ast = babel.transform(code, {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 2
      }
    ]
  ]
});
// 用core-js@2 来看看转码后的结果
console.log(ast.code);

// "use strict";

// [1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);
const babel = require('@babel/core');

const code = `[1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);`;
const ast = babel.transform(code, {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3
      }
    ]
  ]
});
// 用core-js@3 来看看转码后的结果
console.log(ast.code);

// "use strict";

// require("core-js/modules/es.array.flat");

// require("core-js/modules/es.array.unscopables.flat");

// [1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);

ok~ 完美验证结论! 代码地址

vue-cli将在version 4 支持core-js 3

图片

思考

不可否认 vue-cli 是一个非常优秀的脚手架,它提供了一个很 nice 的工程化解决方案。

webpack 构建

babel 编译

postcss 兼容

...

我在一些简历上 经常看到 熟练使用 xxx 脚手架,难道我们应该熟练的是使用脚手架吗 ?

我们在享受工具带给我们的便利跟快感时,是不是也应该想想自己对前端工程化了解多少呢 ?

查看原文

迷鹿 赞了文章 · 2019-09-16

不要肆无忌惮地在你的项目中使用 ES78910 了~

图片描述

如果我有故事,你有 star 吗~

故事背景

在一次 code review 中,我在我们的项目(项目基于 vue-cli 3 创建)中找到了这句代码 MDN

[1, 2, [3, 4, [5, 6]]].flat(Infinity); // [1, 2, 3, 4, 5, 6]

嗯嗯~多维数组扁平化,很酷炫霸拽吊炸天~

我再一看兼容性..

图片

打扰了..

先脑补一波互怼的画面

我 : 老哥,你这个 API 是 ES2019 新特性啊,万万使不得啊~

图片

同事: 我有 vue-cli 3 啊~ 他封装好了 Babel 啊, 我大 vue-cli 3 天下无敌啊~

我 : 我...我真想跳起来打他的膝盖啊~ 凭一句话 好像是毫无说服力啊,是时候表演真正的技术了..

说(睡)服同事

core-js

Modular standard library for JavaScript. Includes polyfills for ECMAScript up to 2019: promises, symbols, collections, iterators, typed arrays, many other features, ECMAScript proposals, some cross-platform WHATWG / W3C features and proposals like URL. You can load only required features or use it without global namespace pollution.

core-js 是 babel 转码的核心包,它使用 es5 API实现了一些 ECMAScript 到 2019 年的 polyfills,并且提供按需加载,且使用它不污染全局名称空间。

@babel/preset-env

@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!

@babel/preset-env 是一个智能插件集合,允许您使用最新的 JavaScript API,而不需要对目标环境所需的API转换(以及可选的 browser polyfills)进行微管理。这不仅使您的生活更轻松,而且 JavaScript 包也更小!

下面我们简单了解一下它的其中两个核心配置项.

useBuiltIns

"usage" | "entry" | false, defaults to false.

(提供"usage" | "entry" | false 三个配置项,默认值为 false)

This option configures how @babel/preset-env handles polyfills.

(这个配置项用来决定@babel/preset-env 如何处理 polyfills)

When either the usage or entry options are used, @babel-preset-env will add direct references to core-js modules as bare imports (or requires). This means core-js will be resolved relative to the file itself and needs to be accessible.

(当使用 usage 或 entry 配置项时,@babel-preset-env 将直接(entry)引用(或按需(usage)引入)core-js 模块,这意味着 core-js 将对文件本身进行解析)

Since @babel/polyfill was deprecated in 7.4.0, we recommend directly adding core-js and setting the version via the corejs option.

(由于@babel/polyfill 在 7.4.0 中被弃用,我们建议直接添加 core-js 并通过 corejs 选项设置版本。)

corejs

2, 3 or { version: 2 | 3, proposals: boolean }, defaults to 2.

(指定 corejs 版本,2 或 3,默认值为 2)

This option only has an effect when used alongside useBuiltIns: usage or useBuiltIns: entry, and ensures @babel/preset-env injects the correct imports for your core-js version.

(此选项只有在 useBuiltIns 选项配置为 entry 或 usage 时才生效,并确保@babel/preset-env 为您的 core-js 版本注入正确的引入)

Ok,接下来我们来看一哈 Vue-cli 3 的 babel 配置~

// babel.config.js
module.exports = {
  presets: ['@vue/app']
};

可以看到 vue-cli 3 这边用的预设集合是自己封装的@vue/app,我们在 node_modules 找到@vue/app 的 package.json

图片

// package.json

"dependencies":{

  "@babel/preset-env": "^7.0.0 < 7.4.0",

  "core-js": "^2.6.5"
}

可以看到依赖里有 core-js 2.x 版本和@babel/preset-env ~

打开@vue/app 的 index.js

//index.js  部分代码

const envOptions = {
  spec,
  loose,
  debug,
  modules,
  targets,
  useBuiltIns, //  划重点,此处值 已定义为 'usage'
  ignoreBrowserslistConfig,
  configPath,
  include,
  exclude: polyfills.concat(exclude || []),
  shippedProposals,
  forceAllTransforms
};

presets.unshift([require('@babel/preset-env'), envOptions]);

由上,我们可以得出结论,vue-cli 使用的 vue-preset-app 封装了@babel/preset-env`,且配置

useBuiltIns: 'usage';

corejs 没做配置,所以为默认值 2

useBuiltIns: 'usage';
corejs: 2;

这么一看,结合我们上面所讲知识,flat 是应该会被转成 es5 咯 ? 啪啪啪,打脸?

倔强的我上 github 打开了 core-js

图片

奇怪的是,我在 core-js 2.65 版本里并没有找到 flat API的实现.

图片

求知欲爆炸的我,翻了 core-js@3 的文档,找到了以下这段话

图片

发现 Array.prototype.flat API是在 core-js@3 才加入的。

图片

结论

vue-cli 3 使用的是 core-js2.x 版本,所以并不能转义 Arrary.prototype.flat 这个API。

实践

得出理论 不实践一波 好像不符合我的风格啊~

npm init -y

npm i @babel/core @babel/preset-env -D
const babel = require('@babel/core');

const code = `[1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);`;
const ast = babel.transform(code, {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 2
      }
    ]
  ]
});
// 用core-js@2 来看看转码后的结果
console.log(ast.code);

// "use strict";

// [1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);
const babel = require('@babel/core');

const code = `[1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);`;
const ast = babel.transform(code, {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3
      }
    ]
  ]
});
// 用core-js@3 来看看转码后的结果
console.log(ast.code);

// "use strict";

// require("core-js/modules/es.array.flat");

// require("core-js/modules/es.array.unscopables.flat");

// [1, 2, 3, 4, [5, 6, [7, 8]]].flat(Infinity);

ok~ 完美验证结论! 代码地址

vue-cli将在version 4 支持core-js 3

图片

思考

不可否认 vue-cli 是一个非常优秀的脚手架,它提供了一个很 nice 的工程化解决方案。

webpack 构建

babel 编译

postcss 兼容

...

我在一些简历上 经常看到 熟练使用 xxx 脚手架,难道我们应该熟练的是使用脚手架吗 ?

我们在享受工具带给我们的便利跟快感时,是不是也应该想想自己对前端工程化了解多少呢 ?

查看原文

赞 25 收藏 11 评论 12

迷鹿 赞了文章 · 2019-09-08

寻根问底之——元素隐藏你知多少?

老生常谈之display: none

相信小伙伴们都被问过这样一个问题:让一个元素隐藏起来,有多少种方法呢?常规来讲,我们有三种方法display: noneopacity: 0visibility: hidden ,基于display: none的副作用,已经是个被说烂的问题,主要是有以下缺点:

一、切换显隐时会导致reflow(回流),从而引起repaint(重绘),当页面中reflow增多至一定程度时,会导致cpu使用率飙高。

二、无法对元素设置过渡动画,也无法进行方位测量(包括clientWidth, clientHeight, offsetWidth, offsetHeight, scrollWidth, scrollHeight, getBoundingClientRect(), getComputedStyle())

原因是:浏览器会解析HTML标签生成DOM Tree,解析CSS生成CSSOM,然后将DOM Tree和CSSOM合并生成Render Tree,最后才根据Render Tree的信息布局来渲染界面,但设置了display: none的元素,是不会被加入Render Tree中的,自然也无法渲染过渡动画。

三、用它来设置显隐切换时,会因为与display: flexdisplay: grid冲突而使人困扰。

你真的了解opacity和visibility吗

如此说来,我们设置元素显隐时,使用opacity或visibility似乎是更好的选择,但小伙伴们有没有考虑过,opacity: 0visibility: hidden 这两者又有何具体区别呢?
既然标题写着寻根问底,那么我们就通过几轮PK来深挖一下这两者的具体区别:

第一轮:动画属性

常见的动画效果中,使用最广泛的应该就属淡入和淡出了,这时候,我们应该只有一种选择:opacity配合animation,因为visibility这个属性是无法进行动画过渡的,要满足动画过渡,必须在两个值之间存在连续不断的值,即连续区间,visibility显然不满足,因为在可见/不可见两个状态之间不存在中间态,它是“布尔隐藏”的。

第二轮:子元素的表现

设置了opacity: 0visibility: hidden 的元素,它们的子元素会受到怎样的不同影响呢?

首先,opacity属性是不可以被子元素继承的,而visibility属性可以被继承,详见CSS3规范opacityvisibility中的属性介绍。

其次,一旦父级元素设置了opacity,那么子元素的最大透明度将无法超过父级,意味着,父级的opacity为0.5,那么子级的opacity就算设置为1,其实际透明度也会是0.5 * 1 = 0.5,所以,只要父级透明度为0,那么子级没有任何办法可以重新设置为可见;

但visibility的子级却仍有“翻身”的机会,即使父级元素设置了visibility: hidden,子元素仍可通过visibility: visible重新设置为可见。

第三轮:层叠上下文(Stacking Context)

HTML中的元素都有自身的层叠水平,但是某些情况下,元素会形成层叠上下文(接下来用SC代替),直接“拔高”自身以及子元素的层叠水平。而元素间不同的层叠水平,在它们发生重叠的时候,就会决定谁将在Z轴上更高一筹,也就是谁离用户的眼睛更近。

至于什么情况下元素会形成SC,可以参考MDN文档的详细说明。而在这份文档中我们可以看到:当元素的opacity属性值小于1时,会形成SC。我们可以观察如下代码:

<div style="position: relative;">
    <div style="position: absolute;background: green;
                top: 0;width: 200px;height: 200px">
    </div>
    <div style="background: red;width: 100px;height: 100px"></div>
</div>

这种情况下,设置了绝对定位的绿色方块形成了SC,所以其层叠水平自然比红色方块高,所以此时我们看不到红色方块:

而当我们为红色方块设置了opacity属性后,比如:

<div style="position: relative;">
    <div style="position: absolute;background: green;
                top: 0;width: 200px;height: 200px">
    </div>
    <div style="opacity: 0.5;background: red;width: 100px;height: 100px">
    </div>
</div>

此时,红色方块会层叠在绿色方块之上。因为红色方块的opacity小于1,形成了SC,且两者都未设置z-index,属于相同层叠水平,所以按照后来居上的原则,红色方块就会叠在上方,如图所示:

同理,opacity为0的元素也会创建SC,而visibility属性则不会创建SC,也不会影响到元素的层叠水平。

说了半天,有人可能会问,既然元素都隐藏了,看不见了,谁还管它在上在下呢?通常情况下是如此,但经过第四轮的PK后,你就会知道,有时候你的确不能忽视这个问题。

第四轮:可交互性/可访问性

这一轮我们比较的是可交互性/可访问性,先说visibility: hidden,设置了这个属性的元素,其绑定的监听事件将会忽略event.target为自身的事件触发。这句话比较拗口,通俗点说就是,这个元素会接收到子元素的事件冒泡,但无法触发自身的事件,可以通过这个在线demo体验一下这个效果。

当然,除了无法触发自身的事件之外,它还无法通过tab键访问到,也就是无法focus;此外,它还会失去accessibility,也就是不能进行无障碍访问,比如屏幕阅读软件将无法访问到这个元素。

反观设置了opacity: 0的元素,则完全没有以上的限制。现在你知道我们为啥不能忽视上一轮提出的问题了,因为设置了opacity: 0的元素即使看不见了,它仍然可以被点击被访问,有时会产生意料之外的bug。

取长补短

既然两者都有各自的优缺点,我们能否将其结合,并取长补短呢?

答案是当然可以。但首先要明确我们想取什么长,补什么短。一般来讲,我们既希望元素可以使用淡入淡出的动画效果,又希望在消失后不要保留可交互性/可访问性,其实做法很简单:

.box {
  animation: fade 0.5s linear 0s forwards;
}
@keyframes fade {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    visibility: hidden;
  }
}

我们仍然使用opacity来做动画过渡,但在最后一个动画帧,我们把visibility: hidden加上,就可以达到我们想要的效果了。此时,当元素淡出后,也不会意外地触发事件了。并且,在使用opacity属性进行动画效果时,浏览器还会将该元素提升为composite layer(合成层),使用gpu进行硬件加速渲染,两全其美~

当然,如果你的确需要这个元素保留页面中的占位,就不能这样做了。

总结

总而言之,如果你没有动画需求,使用visibility进行显隐切换可能更省心,但如果有动画需求,则最好使用两者结合的方式。另外,以后会有更多的寻根问底系列的文章,目的就是要对小的知识点也进行深入剖析,从而获得更加系统性的认识,而不是停留在表面。

ps:欢迎关注微信公众号——前端漫游指南,会定期发布优质原创文章和译文,关注公众号福利:回复666可以获得精选前端进阶电子书,感谢~

图片描述

查看原文

赞 25 收藏 22 评论 2

迷鹿 收藏了文章 · 2019-09-08

JS宽高(client、offset、scroll)的介绍

问题

今日头条的一道笔试题,offsetWidth、clientWidth、scrollWidth的区别。

分析

JS中document对象的宽高有三种,cilentoffsetscroll

client: clientWidth和clientHeight,clientTop和clientLeft

clientWidth和clientHeight:该属性指的是元素的可视部分宽度和高度,即padding+content,统分为三种情况:

  1. 假如元素无padding无滚动
    clientWidth = style.width
  2. 假如元素有padding无滚动
    clientWidth = style.width + style.padding*2
  3. 假如元素有padding有滚动,且滚动是显示的
    clientWidth = style.width + style.padding*2 - 滚动轴宽度
    clientHeight同理

clientTop和clientLeft :这一对属性是用来读取元素的border的宽度和高度的

  • clientTop = border-top 的 border-width
  • clientLeft = border-left 的 border-width
        body {
            width: 500px;
            height: 500px;
            border: 20px solid red;
            margin: 20px;
            padding: 10px;
        }
        div {
            width: 300px;
            height: 300px;
            margin: 10px;
            padding: 20px;
            border: 20px solid gray;
            overflow: auto;
        }
   
        <body>
            <div id="app">
                我是文字<br>
                我是文字<br>
                我是文字<br>
                我是文字<br>
                我是文字<br>
            </div>
        </body>

结果如图:

图片描述

offset:offsetWidth和offsetHeight,offsetTop和offsetLeft

offsetWidth和offsetHeight:这一对属性指的是元素的border+padding+content的宽度和高度
该属性和其内部的内容是否超出元素大小无关,只和本来设置的border以及widthheight有关。统分为三种情况:

  1. 假如元素无padding无滚动无border
    offsetWidth = clientWidth = style.width
  2. 假如元素有padding无滚动有border
    offsetWidth = style.width = style.padding*2 + border宽度*2
  3. 假如元素有padding有滚动有border,且滚动是显示的
    offsetWidth = style.width = style.padding*2 + (border-width)*2
    offsetWidth = clientWidth + 滚轴宽度 + border宽度*2
    offsetHeight同理

offsetTop和offsetLeft:这个需要先了解offsetParent,规则如下

  1. 如果当前元素的父级元素没有进行css定位(position为absolute或relative),offsetParent为body
  2. 如果当前元素的父级元素中有ccss定位(position为absolute或relative),offsetParent取最近的那个父级元素

offsetTop和offsetLeft的计算规则如下:

  • 在IE8/9/10以及chrome中
    offsetLeft = (offsetParent的margin-left) + (offsetParent的border宽度) + (offsetParent的padding-left) + (当前元素的margin-left)
  • 在FireFox中
    offsetLeft = (offsetParent的margin-left) + (offsetParent的padding-left) + (当前元素的margin-left)
    offsetTop同理

style和body代码同上。结果如图:

图片描述

scroll:scrollWidth和scrollHeight,scrollTop和scrollLeft

scrollWidth和scrollHeight:细分为body和其他元素,这里不对body做介绍(详细介绍请点击)

在某div中scrollWidth和scrollHeight计算规则如下
  1. 无滚动轴时:
    scrollWidth = clientWhidth = style.width + style.padding*2
  2. 有滚动轴时:
    scrollWidth = 实际内容的宽度 + padding*2
    scrollHeight同理

scrollTop和scrollLeft:这对元素是可读写的,指的是当元素其中的内容超出其宽高的时候,元素被卷起的宽度和高度。

图片描述

style和body代码同上。结果如图:

图片描述

更详细的介绍,请点击

查看原文

认证与成就

  • 获得 33 次点赞
  • 获得 14 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 14 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-08-08
个人主页被 751 人浏览