前端开发 大前端 W3Cbest

一个专注 WEB 开发的技术博客

0%

当我们想给一个矩形或其他能用 border-radius 生成的形状(在“自适应的椭圆”一节中可以看到一些示例)加投影时,box-shadow 的表现都堪称完美。但是,当元素添加了一些伪元素或半透明的装饰之后,它就有些力不从心了,因为 border-radius 会无耻地忽视透明部分。这类情况包括:

  • 半透明图像、背景图像、或者 border-image(比如老式的金质像框);
  • 元素设置了点状、虚线或半透明的边框,但没有背景(或者当background-clip 不是 border-box 时);
  • 对话气泡,它的小尾巴通常是用伪元素生成的;

如果我们打算对这类元素直接应用 box-shadow,那只能得到不完全投影的结果。难道我们只能完全放弃投影效果吗?有没有办法可以解决这个难题? 滤镜效果规范(http://w3.org/TR/filter-effects)为这个问题提供了一个解决方案。它引入了一个叫作 filter 的新属性,这个属性也是从 SVG 那里借鉴过来的。尽管 CSS 滤镜基本上就是 SVG 滤镜,但我们并不需要掌握任何SVG 知识。相反,只需要一些函数就可以很方便地指定滤镜效果了,比如blur()、grayscale() 以及我们需要的 drop-shadow() !如果你喜欢,甚至可以把多个滤镜串连起来,只要用空格把它们分隔开就可以了,比如:

filter: blur() grayscale() drop-shadow();

drop-shadow() 滤镜可接受的参数基本上跟 box-shadow 属性是一样的,但不包括扩张半径,不包括 inset 关键字,也不支持逗号分割的多层投影语法。举个例子,上面的投影:

box-shadow: 2px 2px 10px rgba(0,0,0,.5);

可以这样来写:

filter: drop-shadow(2px 2px 10px rgba(0,0,0,.5));

CSS 滤镜最大的好处在于,它们可以平稳退化:当浏览器不支持时,不会出现问题,只不过没有任何效果而已。如果你确实需要这个效果在尽可能多的浏览器中显示出来,可以同时附上一个 SVG 滤镜,这样可以得到稍微好一些的浏览器支持度。你可以在滤镜效果规范(http://www.w3.org/TR/filter-effects/)中为每个滤镜函数找到对应的 SVG 滤镜版本。你可以把 SVG滤镜和简化的 CSS 滤镜放在一起使用,让层叠机制来决定哪一行最终生效:

filter: url(drop-shadow.svg#drop-shadow);
filter: drop-shadow(2px 2px 10px rgba(0,0,0,.5));

不幸的是,如果 SVG 滤镜是存放在一个独立文件里的,那它就无法像一个简洁易用的函数那样在 CSS 代码中进行随意配置;如果它是内联的,则又会搅乱你的代码。参数需要写死在文件内部,因此每当我们新加一种哪怕是大同小异的投影效果时,都需要多准备一个文件,这显然是难以接受的。当然,我们还可以使用 data URI(它也会省掉额外的 HTTP 请求),但这个方法仍然会带来文件体积的增长。总的来说,这个方法只是一种回退方案,因此只要我们把SVG 滤镜控制在一定数量以内,哪怕它们的效果大同小异,也是说得过去的。 另外一件需要牢记的事情就是,任何非透明的部分都会被一视同仁地打上投影,包括文本(如果背景是透明的),正如我们刚刚看到的那样。你可能会想,是不是可以通过 text-shadow: none; 来取消掉文本上的投影呢?其实 text-shadow 跟它是完全不相干的两码事,因此这样做并不能取消文本上的 drop-shadow() 效果。此外,如果你已经用 textshadow在文本上加了投影效果,文本投影还会被 drop-shadow() 滤镜再加上投影,这本质上是给投影打了投影!看看下面这段示例代码(请原谅它惨不忍睹的效果,这样只是为了凸显这个怪异的问题):

color: deeppink;
border: 2px solid;
text-shadow: .1em .2em yellow;
filter: drop-shadow(.05em .05em .1em gray);

你可以看到它的渲染效果,图中的文字被同时打上了 textshadow和 drop-shadow()。

常用的 class、id、属性 选择器都可以使用 document.querySelectordocument.querySelectorAll 替代。区别是

  • document.querySelector 返回第一个匹配的 Element
  • document.querySelectorAll 返回所有匹配的 Element 组成的 NodeList。它可以通过 [].slice.call() 把它转成 Array
  • 如果匹配不到任何 Element,jQuery 返回空数组 [],但 document.querySelector 返回 null,注意空指针异常。当找不到时,也可以使用 设置默认的值,如 document.querySelectorAll(selector) []

注意:document.querySelector 和 document.querySelectorAll 性能很。如果想提高性能,尽量使用 document.getElementByIddocument.getElementsByClassName 或 document.getElementsByTagName

探讨一下如果不需要支持过于陈旧的浏览器版本,如何用JavaScript(ES6)标准语法,取代jQuery的一些主要功能

选取元素

选择器查询

常用的 class、id、属性 选择器都可以使用 document.querySelector 或 document.querySelectorAll 替代。

  • document.querySelector 返回第一个匹配的 Element
  • document.querySelectorAll 返回所有匹配的 Element 组成的 NodeList。

jQuery:

var $ele = $(“selector”);

Native:

let ele = document.querySelectorAll(“selector”);

选择器模式

选择器

示例

示例说明

.class

.intro

选择所有class=”intro”的元素

#id

#firstname

选择所有id=”firstname”的元素

*

*

选择所有元素

element

p

选择所有

元素

element,element

div,p

选择所有

元素和

元素

element element

div p

选择

元素内的所有

元素

element>element

div>p

选择所有父级是

元素的

元素

element+element

div+p

选择所有紧接着

元素之后的

元素

[attribute=value]

a[target=_blank]

选择所有使用target=”_blank”的元素

[attribute^=value]

a[src^=”http”]

选择每一个src属性的值以”http”开头的元素

[attribute$=value]

a[src$=”.jpg”]

选择每一个src属性的值以”.jpg”结尾的元素

:first-child

ul li:first-child

选择

    元素下的首个
  • 元素

    :nth-child(n)

    ul li:nth-child(3)

    选择

      元素下的第三个
    • 元素

      :last-child

      ul li:last-child

      选择

        元素下的最后一个
      • 元素

         

        DOM 树查询

        jQuery

        Native

        方法说明

        $ele.parent()

        ele.parentNode

        元素的直接父元素

        $ele.children()

        ele.childNodes

        元素的所有直接子元素

        $ele.find("a")

        ele.querySelectorAll("a")

        元素的后代元素

        $ele.prev()

        ele.previousElementSibling

        元素的上一个同胞元素

        $ele.next()

        ele.nextElementSibling

        元素的下一个同胞元素

        DOM 操作

        DOM本身就具有很丰富的操作方法,可以取代jQuery提供的操作方法。

        内容和属性

        jQuery

        Native

        方法说明

        var text = $ele.text()

        let text = ele.innerText

        获取所选元素的文本内容

        $ele.text("text")

        ele.innerText = "text"

        设置所选元素的文本内容

        var html = $ele.html()

        let html = ele.innerHTML

        获取所选元素的HTML内容

        $ele.html("<div>html</div>")

        ele.innerHTML = "<div>html</div>"

        设置所选元素的HTML内容

        var input = $ele.val()

        let input = ele.value

        获取表单字段的值

        $ele.val("input")

        ele.value = "input"

        设置表单字段的值

        var href = $ele.attr("href")

        let href = ele.getAttribute("href")

        获取元素的属性值

        $ele.attr("href", "/")

        ele.setAttribute("href", "/")

        设置元素的属性值

        修改 DOM 树

        jQuery

        Native

        方法说明

        $parent.append($ele)

        parent.appendChild(ele)

        在被选元素的结尾插入内容

        $parent.prepend($ele)

        parent.insertBefore(ele, parent.firstChild)

        在被选元素的开头插入内容

        $ele.after(html)

        ele.insertAdjacentHTML("afterend", html)

        在被选元素之后插入内容

        $ele.before(html)

        ele.insertAdjacentHTML("beforebegin", html)

        在被选元素之前插入内容

        $ele.remove()

        ele.parentNode.removeChild(ele)

        删除被选元素及其子元素

        $ele.empty()

        ele.innerHTML = null

        从被选元素中删除子元素

        $ele.clone()

        ele.cloneNode(true)

        拷贝被选元素

        $ele.replaceWith(html)

        ele.outerHTML = html

        指定HTML替换被选元素

        CSS 样式

         

        设置 Style

        HTML DOM 允许 JavaScript 改变 HTML 元素的样式,Native API 提供了如下几种方式:

        • ele.setAttribute 直接修改 DOM style 属性改变样式
        • ele.style.cssText 通过 cssText 修改 Style 属性
        • ele.style.property 通过 style 对象读写行内 CSS 样式

        jQuery:

        var size = $ele.css(“font-size”); // 返回第一个匹配元素的 CSS 属性值
        $ele.css(“font-size”, “2rem”); // 为所有元素设置指定的 CSS 属性值

        Native:

        let size = getComputedStyle(ele)[“font-size”]; // 获取当前元素计算后的 CSS 属性值
        ele.style.setProperty(“font-size”, “2rem”); // 设置当前元素的某个内联样式
        ele.style.removeProperty(“font-size”); // 移除当前元素的某个内联样式

        设置 Class

        jQuery

        Native

        方法说明

        $ele.hasClass(className)

        ele.classList.contains(className)

        检查元素是否包含指定的类名

        $ele.addClass(className)

        ele.classList.add(className)

        向元素增加一个或多个类名

        $ele.removeClass(className)

        ele.classList.remove(className)

        从元素中移除一个或多个类

        $ele.toggleClass(className)

        ele.classList.toggle(className)

        对元素的一个或多个类进行切换

        事件方法

        绑定事件

        jQuery:

        $ele.on(“click”, function (evt) {
        console.log(evt.target);
        });

        Native:

        ele.addEventListener(“click”, evt => {
        console.log(evt.target);
        });

        解除绑定 jQuery:

        $ele.off(“click”);

        Native:

        ele.removeEventListener(“click”, func);

        如果要移除事件,addEventListener 必须使用外部函数,绑定匿名函数的事件是无法移除的。

        模拟触发

        jQuery:

        $ele.trigger(“click”);

        Native:

        let event = document.createEvent(“MouseEvents”);
        event.initMouseEvent(“click”);
        ele.dispatchEvent(event);

        模拟事件:

        • 首先通过 document.createEvent 方法创建 Event 对象。
        • 然后利用 Event 对象的 init 方法对其进行初始化。
        • 最后使用 dispatchEvent 方法触发 Event 对象。

        工具

        Array

        jQuery

        Native

        方法说明

        $.isArray(array)

        Array.isArray(array)

        判断参数是否为一个数组

        $.inArray(item, array)

        array.includes(item)

        判断值是否在指定数组中

        $.makeArray(objlist)

        Array.from(objlist)

        将类数组对象转换为数组

        $.merge(array1, array2)

        array1.concat(array2)

        合并两个数组(有区别)

        $.each(array, function (i, item) {}

        array.forEach((item, i) => {})

        遍历指定的对象和数组

        合并数组时,merge 会改变原数组的内容,而 concat 不会修改原数组,只会返回合并后的数组

        Method

        jQuery

        Native

        方法说明

        $.now()

        Date.now()

        返回当前时间戳

        $.trim(string)

        string.trim();

        移除字符串头尾空白

        $.type(obj);

        Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();

        检测参数的内部类型

        $.parseJSON(string)

        JSON.parse(string)

        将JSON转换为JS对象

        $ele.data("key", "value")

        ele.dataset.key = "value"

        在指定的元素上存储数据

        $.map(array, function(value, index){ });

        array.map(function(value, index){ });

        将数组转化为处理后的新数组

        参考:https://github.com/nefe/You-Dont-Need-jQuery

在项目开发中用到echarts有这样的需求,比如要给一个星期的某个一天设置一个高亮色与其他天不同的颜色来区分开,那么我来演示一下如何达到这样的需求, 我知道的方法有两种,也可能有其他方法,欢迎来吐槽

方法一

也是在官网上看到的,就是直接在data数据的数组上设置颜色,也就是说在数组的某一段需要添加对象值来达到这种效果

option = {
……
series: [{
data: [120, {
value: 200,
itemStyle:{
color: ‘#f00’
}
}, 150, 80, 70, 110, 130],
……
}]
};

这种方法在处理数据的太麻烦,我不喜欢这种方法

方法二

设置itemStyle图形样式属性的color,这种方法是在formatter的启发下得到的,因为在文档上没有看到color有回调函数,首先要设置一个变量也就是高亮色的位置,这个变量的值可以后端给,也可以前端给(前端给的必定是死值), data数组我们不动,

const curInt = 2;
option = {
series:[{
data:[…],

itemStyle:{
color: function(params){
const key = params.dataIndex;
if(key === curInt){
return ‘#E062AE’;
}else{
return ‘#37A2DA’
}
}
}
}]
};

好了,我知道的就这,方法是否对你有用,我就不知道了。

我们使用CSS3 选择器:target制作一个可伸缩的导航菜单。先理解一下:target

定义和用法

URL 带有后面跟有锚名称 #,指向文档内某个具体的元素。这个被链接的元素就是目标元素(target element)。 :target 选择器可用于选取当前活动的目标元素。 那么我就看一下代码如何做可伸缩的导航菜单

CSS代码

nav {
font-size: 12px;
background-color: rgb(19, 51, 61);
box-shadow: 0 1px 2px rgba(19, 51, 61, 0.5);
margin: 3em 0 6em;
padding: 0 1em;
height: 44px;
overflow: hidden;
}

nav ul {
margin: 0;
padding: 0;
list-style-type: none;
max-height: 88px;
position: relative;
}

nav li {
display: inline-block;
}

nav a {
display: inline-block;
padding: 0 1em;
color: rgb(236, 236, 236);
font-weight: 700;
letter-spacing: 0.1em;
text-decoration: none;
text-transform: uppercase;
white-space: nowrap;
line-height: 44px;
height: 44px;
}

nav a:hover {
background-color: rgba(255, 255, 255, 0.08);
}

nav li:last-child {
/* 菜单按钮 */
position: absolute;
right: 0;
bottom: 44px;
background-image: linear-gradient(to right, rgba(19, 51, 61, 0) 0, rgba(19, 51, 61, 1) 2em);
padding-left: 3em;
}

nav li:nth-last-child(2) {
/* 先关闭按钮 */
display: none;
}

nav#menu:target {
height: auto;
padding: 0;
}

nav#menu:target ul {
max-height: none;
}

nav#menu:target li {
display: block;
}

nav#menu:target a {
display: block;
padding: 0 2em;
background-color: rgba(255, 255, 255, 0.05);
}

nav#menu:target a:hover {
background-color: rgba(255, 255, 255, 0.08);
}

nav#menu:target li:not(:first-child) {
margin-top: 2px;
}

nav#menu:target li:last-child {
display: none;
}

nav#menu:target li:nth-last-child(2) {
display: inline-block;
position: absolute;
top: 0;
right: 0;
margin: 0;
border-left: 2px solid rgb(19, 51, 61);
}

查看demo

如果不处理对象,您就无法在 JavaScript 方面取得很大进展。它们几乎是 JavaScript 编程语言的所有方面的基础。事实上,学习如何创建对象可能是你刚开始学习的第一件事。话虽如此,为了最有效地学习 JavaScript 中的原型,我们将从基础开始。 首先,对象是键/值对。创建对象的最常用方法是使用花括号{},并使用点表示法向对象添加属性和方法。

let animal = {}
animal.name = 'Leo'
animal.energy = 10

animal.eat = function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}

animal.sleep = function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}

animal.play = function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}

这个很简单。现在,我们在应用程序中我们需要创建多个动物。很自然地,下一步就是将逻辑封装到一个函数中,以便我们在需要创建新动物时调用这个函数。我们将调用这个模式 Functional Instantiation(函数实例化),并将函数本身称为 constructor function(构造函数) ,因为它负责“构造”一个新对象。

Functional Instantiation (函数实例化)

function Animal(name, energy) {
    let animal = {}
    animal.name = name
    animal.energy = energy

    animal.eat = function(amount) {
        console.log(`${this.name} is eating.`)
        this.energy += amount
    }

    animal.sleep = function(length) {
        console.log(`${this.name} is sleeping.`)
        this.energy += length
    }

    animal.play = function(length) {
        console.log(`${this.name} is playing.`)
        this.energy -= length
    }

    return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

现在,每当我们想要创建一种新动物(或者更广泛地说是一种新的“实例”)时,我们所要做的就是调用我们的 Animal 函数,将动物的 name 和energy 传递给这个函数。这非常有效,而且非常简单。但是,你有发现这种模式的不足之处吗?我们要尝试解决的最大的问题与三种方法有关 – eatsleep 和 play。这些方法中的每一种都不仅是动态的,而且它们也是完全通用的。这意味着没有理由重新创建这些方法,正如我们在创建新动物时所做的那样。我们只是在浪费内存,让每个动物物体都比它需要的更大。你能想到一个解决方案吗? 如果我们每次创建一个新动物时不需要重新创建这些方法,而是将它们移动到它们自己的对象上,那么我们就可以让每个动物引用那个对象了?我们可以把这种模式称为 Functional Instantiation with Shared Methods(共享方法的函数实例化) 。描述起来有点啰嗦。

Functional Instantiation with Shared Methods (共享方法的函数实例化)

const animalMethods = {
    eat(amount) {
        console.log(`${this.name} is eating.`)
        this.energy += amount
    },
    sleep(length) {
        console.log(`${this.name} is sleeping.`)
        this.energy += length
    },
    play(length) {
        console.log(`${this.name} is playing.`)
        this.energy -= length
    }
}

function Animal(name, energy) {
    let animal = {}
    animal.name = name
    animal.energy = energy
    animal.eat = animalMethods.eat
    animal.sleep = animalMethods.sleep
    animal.play = animalMethods.play

    return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

通过将共享方法移动到它们自己的对象并在 Animal 函数中引用该对象,我们现在已经解决了内存浪费和动物对象过大的问题。

Object.create

让我们使用 Object.create 再次改进我们的例子。 简而言之,Object.create 允许您创建一个对象,该对象将在查找失败时委托给另一个对象。 换句话说,Object.create 允许您创建一个对象,只要该对象上的属性查找失败,它就可以查询另一个对象,以查看另一个对象中是否具有该属性。 说清楚需要很多文字, 我们来看一些代码。

const parent = {
    name: 'Stacey',
    age: 35,
    heritage: 'Irish'
}

const child = Object.create(parent),
child.name = 'Ryan',
child.age = 7;

console.log(child.name); // Ryan
console.log(child.age); // 7
console.log(child.heritage); // Irish

在上面的示例中,因为 child 是通过 Object.create(parent) 创建的,所以每当在 child 中查找属性失败时,JavaScript 就会将该查找委托给 parent 对象。这意味着即使 child 没有 heritage 属性,当你查找 child.heritage 时你会得到 parent 的heritage 属性,即 Irish。 现在,通过使用 Object.create ,我们该如何使用它来简化之前的 Animal 代码呢?好吧,我们可以使用 Object.create 委托给animalMethods 对象,而不是像我们之前一样逐个将所有共享方法添加到 Animal 中。 为了听起来很智能,让我们称之为 Functional Instantiation with Shared Methods and Object.create(使用共享方法和Object.create进行函数实例化)。

Functional Instantiation with Shared Methods and Object.create (使用共享方法和Object.create进行函数实例化)

const animalMethods = {
    eat(amount) {
        console.log(`${this.name} is eating.`);
        this.energy += amount;
    },
    sleep(length) {
        console.log(`${this.name} is sleeping.`);
        this.energy += length;
    },
    play(length) {
        console.log(`${this.name} is playing.`);
        this.energy -= length;
    }
}

function Animal(name, energy) {
    let animal = Object.create(animalMethods);
    animal.name = name;
    animal.energy = energy;

    return animal;
}

const leo = Animal('Leo', 7);
const snoop = Animal('Snoop', 10);

leo.eat(10);
snoop.play(5);

所以现在当我们调用 leo.eat 时,JavaScript 会在 leo 对象上查找 eat 方法。 这个查找将失败,因为使用了 Object.create,它将委托给 animalMethods 对象,然后在这里将找到 eat 方法。 到现在为止还挺好的。 尽管如此,我们仍然可以做出一些改进。 为了跨实例共享方法,必须管理一个单独的对象(animalMethods)似乎有点“hacky”。 这似乎是您希望在语言本身中实现的常见功能。 事实证明,这就是你看这篇文章的原因 – prototype(原型) 。 那么究竟什么是 JavaScript 的 prototype(原型)呢? 简单地说,JavaScript 中的每个函数都有一个引用对象的 prototype 属性。 我们来亲自测试一下。

function doThing () {};
console.log(doThing.prototype); // {}

如果不是创建一个单独的对象(比如我们正在使用的 animalMethods )来管理我们的方法,也就是我们只是将每个方法放在 Animal 函数的 prototype(原型) 对象上,该怎么办呢?我们所要做的就是不使用 Object.create 委托给 animalMethods,我们可以用使用来委托Animal.prototype。 我们将这种模式称为 Prototypal Instantiation(原型实例化)。

Prototypal Instantiation (原型实例化)

function Animal(name, energy) {
    let animal = Object.create(Animal.prototype);
    animal.name = name;
    animal.energy = energy;

    return animal;
}

Animal.prototype.eat = function(amount) {
    console.log(`${this.name} is eating.`);
    this.energy += amount;
}

Animal.prototype.sleep = function(length) {
    console.log(`${this.name} is sleeping.`);
    this.energy += length;
}

Animal.prototype.play = function(length) {
    console.log(`${this.name} is playing.`);
    this.energy -= length;
}

const leo = Animal('Leo', 7);
const snoop = Animal('Snoop', 10);

leo.eat(10);
snoop.play(5);

这里你可以为自己鼓掌鼓励一下了。 同样,原型只是 JavaScript 中每个函数都具有的属性,并且如上所述,它允许我们在函数的所有实例之间共享方法。 我们所有的功能仍然相同,但现在我们不必为所有方法管理一个单独的对象,我们可以使用另一个内置于 Animal 函数本身的对象Animal.prototype

更深的,走起!

首先,我们需要知道三件事:

  1. 如何创建构造函数。
  2. 如何将方法添加到构造函数的原型中。
  3. 如何使用 Object.create 将失败的查找委托给函数的原型。

这三个任务似乎是任何编程语言的基础。 JavaScript 是否真的那么糟糕,没有更简单,“内置”的方式来完成同样的事情? 正如你可能已经猜测的那样,它是通过使用 new 关键字。 我们采用的这种缓慢而有条理的方法的好处是,您现在可以深入了解 JavaScript 中的 new 关键字在幕后的作用。 回顾一下我们的 Animal 构造函数,最重要的两个部分是创建对象并返回它。 如果不使用 Object.create创建对象,我们将无法在查找失败时委托给函数的原型。 如果没有 return 语句,我们将永远不会返回创建的对象。

function Animal(name, energy) {
    let animal = Object.create(Animal.prototype);
    animal.name = name;
    animal.energy = energy;
    return animal;
}

new 有一个很酷的地方——当您使用 new 关键字调用函数时,注释掉的这两行代码是隐式(引擎)完成的,创建的对象称为 this。 使用注释来显示在幕后发生的事情并假设使用 new 关键字调用 Animal 构造函数,可以将其重写为这样:

function Animal(name, energy) {
    // const this = Object.create(Animal.prototype)
    this.name = name
    this.energy = energy
    // return this
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

去掉注释后:

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}
Animal.prototype.eat = function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}
Animal.prototype.sleep = function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}
Animal.prototype.play = function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

同样,这样做以及为我们创建 this 对象的原因是,我们使用 new 关键字调用构造函数。如果在调用函数时不使用 new ,则该对象永远不会创建,也不会隐式返回。我们可以在下面的例子中看到这个问题。

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}
const leo = Animal('Leo', 7)
console.log(leo) // undefined

此模式的名称是 Pseudoclassical Instantiation(伪类实例化) 。 如果 JavaScript 不是您的第一种编程语言,您可能会有点不安。

“WTF这个家伙只是重新创造了一个更糟糕的版本” – 你

对于那些不熟悉的人,Class(类) 允许您为对象创建模板。然后,无论何时创建该类的实例,都会获得一个具有模板中定义的属性和方法的对象。 听起来有点熟悉?这基本上就是我们对上面的 Animal 构造函数所做的事情。但是对于 Animal 构造函数,我们只使用常规的旧 JavaScript 函数来重新创建相同的功能,而不是使用 class 关键字。当然,它需要一些额外的工作以及一些关于 JavaScript “引擎” 所处理的事情的相关知识,但结果是一样的。 这是个好消息。 JavaScript 不是一种 “死” 语言。它不断得到改进,并由 TC-39委员会 不断的制定标准。这意味着即使 JavaScript 的初始版本不支持类,也不影响后续将它们添加到官方规范中。事实上,这正是TC-39委员会所做的事情。 2015年,发布了 EcmaScript(官方JavaScript规范)6 ,支持 Classes(类) 和 class 关键字。让我们看看上面的 Animal 构造函数如何使用新的 class(类) 语法。

class Animal {
    constructor(name, energy) {
        this.name = name
        this.energy = energy
    }
    eat(amount) {
        console.log(`${this.name} is eating.`)
        this.energy += amount
    }
    sleep(length) {
        console.log(`${this.name} is sleeping.`)
        this.energy += length
    }
    play(length) {
        console.log(`${this.name} is playing.`)
        this.energy -= length
    }
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

很干净是吧? 因此,如果这是创建类的新方法,为什么我们前面花了这么多时间来讨论旧的方式呢? 原因是新的方式(使用 class 关键字)只是经典伪类模式的 “语法糖”。 为了完全理解 ES6 类的便捷语法,首先必须理解经典的伪类模式。 我们已经介绍了 JavaScript 原型的基础知识。 本文的其余部分将致力于加深理解相关知识的主题。 在另一篇文章中,我们将看看如何利用这些基础知识并使用它们来理解 JavaScript 中继承的工作原理。

数组方法

我们在上面深入讨论了如何在类的实例之间共享方法,您应该将这些方法放在类(或函数)原型上。 如果我们查看 Array 类,我们可以看到相同的模式。 从历史上看,您可能已经创建了这样的数组:

const friends = []

事实证明,这只是创建一个新的 Array 类实例的语法糖。

const friendsWithSugar = []
const friendsWithoutSugar = new Array()

您可能从未想过:数组的每个实例是如何具有所有内置方法的(splice , slicepop 等)? 正如您现在所知,这是因为这些方法存在于 Array.prototype 上,当您创建一个新的 Array 实例时,您使用 new 关键字在查找失败时将该委托设置为 Array.prototype 。 我们可以通过简单地 console.log(Array.prototype) 来查看所有数组的方法。

console.log(Array.prototype)
/*
  concat: ?n concat()
  constructor: ?n Array()
  copyWithin: ?n copyWithin()
  entries: ?n entries()
  every: ?n every()
  fill: ?n fill()
  filter: ?n filter()
  find: ?n find()
  findIndex: ?n findIndex()
  forEach: ?n forEach()
  includes: ?n includes()
  indexOf: ?n indexOf()
  join: ?n join()
  keys: ?n keys()
  lastIndexOf: ?n lastIndexOf()
  length: 0n
  map: ?n map()
  pop: ?n pop()
  push: ?n push()
  reduce: ?n reduce()
  reduceRight: ?n reduceRight()
  reverse: ?n reverse()
  shift: ?n shift()
  slice: ?n slice()
  some: ?n some()
  sort: ?n sort()
  splice: ?n splice()
  toLocaleString: ?n toLocaleString()
  toString: ?n toString()
  unshift: ?n unshift()
  values: ?n values()
*/

Objects(对象) 也是完全相同的逻辑。 所有对象将在查找失败时委托给 Object.prototype ,这就是所有对象都有 toString 和hasOwnProperty 等方法的原因。

静态方法

到目前为止,我们已经介绍了为什么,以及如何在类的实例之间共享方法。 但是,如果我们有一个对 Class 很重要但不需要又跨实例共享的方法,该怎么办呢? 例如,如果我们有一个函数,它接收一系列 Animal 实例并决定下一个需要喂食的对象,会怎样? 我们将其称为 nextToEat

function nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a, b) => {
        return a.energy - b.energy
    })
    return sortedByLeastEnergy[0].name
}

我们不希望在所有实例之间共享它,所以在 Animal.prototype 上使用 nextToEat 是没有意义的。 相反,我们可以将其视为辅助方法。 所以如果 nextToEat 不应该存在于 Animal.prototype 中,我们应该把它放在哪里呢? 那么显而易见的答案是我们可以将 nextToEat 放在与 Animal 类相同的作用域中,然后像我们平常那样,在需要时引用它。

class Animal {
    constructor(name, energy) {
        this.name = name
        this.energy = energy
    }
    eat(amount) {
        console.log(`${this.name} is eating.`)
        this.energy += amount
    }
    sleep(length) {
        console.log(`${this.name} is sleeping.`)
        this.energy += length
    }
    play(length) {
        console.log(`${this.name} is playing.`)
        this.energy -= length
    }
}
function nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a, b) => {
        return a.energy - b.energy
    })

    return sortedByLeastEnergy[0].name
}
const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)
console.log(nextToEat([leo, snoop])) // Leo

现在这可行,但有更好的方法。 只要有一个特定于类本身的方法,但不需要在该类的实例之间共享,就可以将其添加为类的 static(静态) 属性。

class Animal {
    constructor(name, energy) {
        this.name = name
        this.energy = energy
    }
    eat(amount) {
        console.log(`${this.name} is eating.`)
        this.energy += amount
    }
    sleep(length) {
        console.log(`${this.name} is sleeping.`)
        this.energy += length
    }
    play(length) {
        console.log(`${this.name} is playing.`)
        this.energy -= length
    }
    static nextToEat(animals) {
        const sortedByLeastEnergy = animals.sort((a, b) => {
            return a.energy - b.energy
        })

        return sortedByLeastEnergy[0].name
    }
}

现在,因为我们在类上添加了 nextToEat 作为 static(静态) 属性,所以它存在于 Animal 类本身(而不是它的原型)中,并且可以使用 Animal.nextToEat 进行访问。

const leo = new Animal('Leo', 7) 
const snoop = new Animal('Snoop', 10) 
console.log(Animal.nextToEat([leo, snoop])) // Leo

因为我们在这篇文章中都遵循了类似的模式,让我们来看看如何使用 ES5 完成同样的事情。 在上面的例子中,我们看到了如何使用 static 关键字将方法直接放在类本身上。 使用ES5,同样的模式就像手动将方法添加到函数对象一样简单。

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}

Animal.prototype.eat = function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}

Animal.prototype.sleep = function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}

Animal.prototype.play = function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}

Animal.nextToEat = function(nextToEat) {
    const sortedByLeastEnergy = animals.sort((a, b) => {
        return a.energy - b.energy
    })

    return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo

获取对象的原型

无论您使用哪种模式创建对象,都可以使用 Object.getPrototypeOf 方法完成获取该对象的原型。

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}

Animal.prototype.eat = function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}

Animal.prototype.sleep = function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}

Animal.prototype.play = function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}

const leo = new Animal('Leo', 7)
const prototype = Object.getPrototypeOf(leo)

console.log(prototype)
// {constructor: ?, eat: ?, sleep: ?, play: ?}

prototype === Animal.prototype // true

上面的代码有两个要点。 首先,你会注意到 proto 是一个对象,有4种方法,constructoreatsleep,和play。那讲得通。我们将实例传递给getPrototypeOfleo 获取了实例的原型,这里是所有的方法。这提示我们,关于原型的另外一件事我们还没有讨论过。默认情况下,原型对象将具有 constructor 属性,该属性指向原始函数或创建实例的类。这也意味着 JavaScript 默认在原型上放置 constructor 属性,所以任何实例都可以通过 instance.constructor 访问它们的构造函数。 上面的第二个要点是 Object.getPrototypeOf(leo) === Animal.prototype 。这也是有道理的。 Animal 构造函数有一个 prototype(原型) 属性,我们可以在所有实例之间共享方法,getPrototypeOf 允许我们查看实例本身的原型。

function Animal(name, energy) {
this.name = name
this.energy = energy
}

const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function

为了配合我们之前使用 Object.create 所讨论的内容,其工作原因是因为任何 Animal 实例都会在查找失败时委托给 Animal.prototype。 因此,当您尝试访问 leo.prototype 时, leo 没有 prototype 属性,因此它会将该查找委托给Animal.prototype,它确实具有 constructor 属性。 如果这段没有看懂,请回过头来阅读上面的 Object.create 。 您可能以前看到过使用 __proto__ 获取实例的原型。 这是过去的遗物。 现在,如上所述使用 Object.getPrototypeOf(instance) 获取实例的原型。

确定属性是否存在于原型上

在某些情况下,您需要知道属性是否存在于实例本身上,还是存在于对象委托的原型上。 我们可以通过循环我们创建的 leo 对象来知道这一点。假设目标是循环 leo 并记录其所有键和值。使用 for in 循环,可能看起来像这样。

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}

Animal.prototype.eat = function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}

Animal.prototype.sleep = function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}

Animal.prototype.play = function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}

const leo = new Animal('Leo', 7)

for (let key in leo) {
    console.log(`Key: ${key}. Value: ${leo[key]}`)
}

你期望看到什么?最有可能的是,它是这样的 –

Key: name. Value: Leo
Key: energy. Value: 7

但是,如果你运行代码,你看到的是这样的 –

Key: name.Value: Leo
Key: energy.Value: 7
Key: eat.Value: function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}
Key: sleep.Value: function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}
Key: play.Value: function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}

这是为什么? for in 循环将循环遍历对象本身以及它所委托的原型的所有 可枚举属性 。 因为默认情况下,您添加到函数原型的任何属性都是可枚举的,我们不仅会看到name 和 energy ,还会看到原型上的所有方法 – eatsleep 和 play 。 要解决这个问题,我们需要指定所有原型方法都是不可枚举的,或者如果属性在 leo 对象本身上而不是 leo 查找失败时委托给的原型上。 hasOwnProperty 可以帮助我们实现这个需求。 hasOwnProperty 是每个对象上的一个属性,它返回一个布尔值,指示对象是否具有指定的属性作为其自身的属性,而不是对象委托给的原型。 这正是我们所需要的。 现在有了这些新知识,我们可以修改我们的代码,以便利用 for in 循环中的 hasOwnProperty 。

const leo = new Animal('Leo', 7)

for (let key in leo) {
    if (leo.hasOwnProperty(key)) {
        console.log(`Key: ${key}. Value: ${leo[key]}`)
    }
}

而现在我们看到的只是 leo 对象本身的属性,而不是 leo 原型中的方法。

Key: name. Value: Leo
Key: energy. Value: 7

如果你仍然对 hasOwnProperty 感到困惑,这里有一些代码可以帮你消除困惑。

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}

Animal.prototype.eat = function(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
}

Animal.prototype.sleep = function(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
}

Animal.prototype.play = function(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
}

const leo = new Animal('Leo', 7)

leo.hasOwnProperty('name') // true
leo.hasOwnProperty('energy') // true
leo.hasOwnProperty('eat') // false
leo.hasOwnProperty('sleep') // false
leo.hasOwnProperty('play') // false

检查对象是否是类的实例

有时您想知道对象是否是指定类的实例。 为此,您可以使用 instanceof 运算符。 用例非常简单,但如果您以前从未见过它,实际的语法有点奇怪。 它的工作原理如下

object instanceof Class

如果 object 是 Class 的实例,则上面的语句将返回 true ,否则返回 false 。回到我们的 Animal 示例,我们会有类似的东西。

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}

function User() {}

const leo = new Animal('Leo', 7)

leo instanceof Animal // true
leo instanceof User // false

instanceof 的工作方式是检查对象原型链中是否存在 constructor.prototype 。 在上面的例子中,leo instanceof Animal 为 true ,因为Object.getPrototypeOf(leo) === Animal.prototype。 另外,leo instanceof User 为 false ,因为 Object.getPrototypeOf(leo) !== User.prototype

创建新的不可知构造函数

你能发现下面代码中的错误吗?

function Animal(name, energy) {
    this.name = name
    this.energy = energy
}

const leo = Animal('Leo', 7)

即使是经验丰富的 JavaScript 开发人员有时也会因为上面的例子而被绊倒。 因为我们正在使用之前学过的 pseudoclassical pattern(经典伪类模式),所以当调用 Animal 构造函数时,我们需要确保使用 new 关键字调用它。 如果我们不这样做,则不会创建 this 关键字,也不会隐式返回。 作为复习,注释掉的行是在函数上使用 new 关键字时幕后所做的事情。

function Animal(name, energy) {
    // const this = Object.create(Animal.prototype)

    this.name = name
    this.energy = energy

    // return this
}

这似乎是一个非常重要的细节,让其他开发人员记住。 假设我们正在与其他开发人员合作,有没有办法确保我们的 Animal 构造函数始终使用 new 关键字调用呢? 事实证明,它是通过使用我们之前学到的 instanceof 运算符来实现的。 如果使用 new 关键字调用构造函数,那么构造函数体的内部将是构造函数本身的实例。 那是很多文字才能说清楚的。 这是一些代码。

function Animal(name, energy) {
    if (this instanceof Animal === false) {
        console.warn('Forgot to call Animal with the new keyword')
    }
    this.name = name
    this.energy = energy
}

现在,如果我们使用 new 关键字重新调用函数,而不是只向函数的使用者打印警告,会发生什么呢?

function Animal(name, energy) {
    if (this instanceof Animal === false) {
        return new Animal(name, energy)
    }
    this.name = name
    this.energy = energy
}

现在无论是否使用 new 关键字调用 Animal,它都可以正常工作。

重新创建 Object.create

在这篇文章中,我们非常依赖于 Object.create 来创建委托给构造函数原型的对象。 此时,您应该知道如何在代码中使用 Object.create ,但您可能没有想到的一件事是Object.create 实际上是如何工作的。 为了让您真正了解 Object.create 的工作原理,我们将重新创建它。 首先,我们对 Object.create 的工作原理了解多少?

  1. 它接受一个对象作为参数。
  2. 它创建一个对象,该对象在查找失败时委托给参数对象。
  3. 它返回新创建的对象。

让我们从第1点开始吧。

Object.create = function (objToDelegateTo) {}

很简单。 现在第2点 – 我们需要创建一个对象,该对象将在查找失败时委托给参数对象。 这个有点棘手。 为此,我们将使用我们对 new 关键字和原型如何在 JavaScript 中工作的知识。首先,在 Object.create 实现的主体中,我们将创建一个空函数。 然后,我们将该空函数的原型设置为参数对象。然后,为了创建一个新对象,我们将使用 new 关键字调用空函数。如果我们返回新创建的对象,也会完成第3点。

Object.create = function(objToDelegateTo) {
    function Fn() {}
    Fn.prototype = objToDelegateTo
    return new Fn()
}

有点野蛮是吧?让我们来看看吧。 当我们在上面的代码中创建一个新函数 Fn 时,它带有一个 prototype 属性。 当我们使用 new 关键字调用它时,我们知道我们将得到的是一个对象,该对象将在查找失败时委托给函数的原型。 如果我们覆盖函数的原型,那么我们可以决定在查找失败时委托给哪个对象。 所以在我们上面的例子中,我们用调用 Object.create 时传入的对象覆盖 Fn的原型,我们称之为 objToDelegateTo。 请注意,我们只支持 Object.create 的单个参数。官方实现还支持第二个可选参数,该参数允许您向创建的对象添加更多属性。

箭头函数

箭头函数没有自己的 this 关键字。因此,箭头函数不能用于构造函数,如果您尝试使用 new 关键字调用箭头函数,它将抛出错误。

const Animal = () => {}
const leo = new Animal() // Error: Animal is not a constructor

另外,因为我们在上面证明了 pseudoclassical pattern(经典伪类模式) 不能与箭头函数一起使用,所以箭头函数也没有 prototype(原型) 属性。

const Animal = () => {}
console.log(Animal.prototype) // undefined

 原文地址:https://tylermcginnis.com/

对于密码的可见性切换经常会在移动端见到,由于移动端输入体验不是很好,经常会输入密码的时候出现错误,反正我是有这样的体验,所以衍生出了密码的显示和隐藏的切换,那么在pc端会出现这种问题吗?答案是会的,特别是在重置密码的时候要输入两次,就会出现输入错误的问题,我是程序员我的解决方法是查看源码。把input的type类型的password改变成text或置空,就显示密码了,这是我经常使用的惯例,所以你在使用浏览器的时候千万不要让浏览器记住密码,很危险的。

那么我们的用户是否也可以看到密码呢,答案是有的,实现原理就是通过js将input 标签 type=”password”改变为type=”text”,在 Chrome、FireFox 等浏览器中通过修改 input 标签的 type 属性轻松实现该效果,但是 IE 下就会报错。如果你需要兼容IE,就必须考虑其他方案。下面看例子:

例子一:用复选框作为介质来切换显示隐藏

最常见的实现是位于密码输入框下的复选框和标签。切换复选框后,密码变为可见。

对于上面提到的安全问题,我已经在密码输入上禁用了自动完成功能。交互非常简单,JavaScript将监听复选框输入的更改。切换后,密码字段将从type属性更改password为text。

在功能方面,这种实现很好。但是有个问题是,“显示密码”复选框文本乍一看就像登录表单更常见的“记住我”选项,不可取。

例子二:用一个按钮实现

实现密码可见性切换的另一种方法是在密码字段本身上设置一个按钮。你也可以用图标来表示。

实现与复选框示例没有太大差别,主要区别在于文本根据密码字段的状态而变化。

我更喜欢这种方法的原因是因为像按钮的作用,至少它看起来不像是“记住我”复选框,它仍然有相同的安全问题。

当你的用户需要漂亮的图标给出额外的文字信息时,亦或是当他们在点击了按钮之后需要确认自己没点错时,又或是带图片和字幕的复活节彩蛋,提示框是用来增强用户界面的绝佳手段。现在,让我们来做几个动画提示框,没有别的,只有HTML和CSS。 样例 这是我们之后要做的:

See the Pen CSS Tooltip Magic by xianzhiding (@xianzhiding) on CodePen.

在我们沉浸在写代码的过程中之前,让我们先来看看我们的意图是什么。主要目的是为了获得一种简单的添加提示框的方法,这样一来,我们之后就能够通过增加一个自定义的 tooltip 属性来做到这一点。

visible text or icon, etc.

让我们设定几个预期

  • 不需要JavaScript
  • 我们将会使用属性选择器(而不是类名),以及CSS内建的模式匹配
  • 加到现有的DOM元素(你的标签中不需要新的元素)
  • 代码例子中是没有前缀的(如有需要,为你的目标浏览器加上供应商前缀)
  • 假设通过 mouseover/hover 来触发提示框
  • 仅仅是纯文本提示框(HTML,图片等等都不支持)
  • 当唤起提示框时,有巧妙的动画

好了,老司机要开车了!

哦,等等。我们还要先处理一个问题,是关于”不需要额外标签”的。毕竟,这很巧妙。 我们的提示框真的不需要额外的DOM元素,因为它们完全是基于伪元素的(::before 和 ::after),我们可以通过CSS来控制。 如果你已经在其它样式集中使用了一个元素的伪元素,又希望在这个元素是加一个提示框,那么你可能需要稍稍做一些重构。 没什么比得上来一场提示框盛会了! 等等。小坏蛋!还有一个警告:CSS定位。为了提示框正常运作,它们的父元素(我们把提示框添加在它后面)需要是

  • position: relative,或者
  • position: absolute,或
  • position: fixed

基本上,什么都行,只要不是 position: static — 这是浏览器赋给几乎所有元素的默认定位模式。提示框是绝对定位的,所以它们需要知道它们的绝对值在什么边界内是有意义的。 默认的定位指令 static 不会声明它的边界,也不会给我们的提示框以上下文来进行相对定位。所以提示框会使用之后,最近的,有声明边界的父元素。 你还需要根据你如何使用提示框来决定哪个定位指令最为合适。这篇教程假设父元素是 postion: relative 如果你的UI依靠一个绝对定位的元素,那么在那个元素上部署一个提示框,也会需要一些重构(额外的标签)。 让我们开始吧。

属性选择器:快速回顾

大多数CSS规则印象中都是用类名写的,比如 .this-thing ,但是CSS有几个类型的选择器。我们巧妙的提示框打算使用属性选择器——也就是方括号表示法。

[foo] {
background: rgba(0, 0, 0, 0.8);
color: #fff;
}

当浏览器看到诸如此类的东西时:

Check it out!

浏览器会知道,它需要应用 [foo] 规则了,因为 标签有一个叫做 foo 的属性。在这个例子中,span自身会有一个半透明的黑色背景,以及白色文字。 HTML元素有着各种各样的内置属性,但是我们也可以给出我们自己的属性。比如 foo ,又或者是 tooltip 。默认情况下,HTML不知道这些东西是什么意思,但是有了CSS,我们可以告诉HTML这些自定义属性是什么意思。

为什么用属性选择器?

我们后面会使用属性选择器,主要是出于侧重分离的目的。使用属性而不是类名,并不会让我们在详细程度上获得更多益处,类和属性在详细程度上是相同的。 然而,通过使用属性,我们可以把我们的内容放在一块儿,因为HTML属性可以有值,而类名没有值。 在这个例子的代码中,来权衡一下类名 .tooltip 对比属性 [tooltip] 。类名是 [class] 属性的值中的一个,而tooltip属性可以存放一个值,它就是我们要显示的文字。

lorem ipsum
lorem ipsum

现在让我们来看看提示框炼金术

我们的提示框会使用两种不同的属性:

  • tooltip: 这个属性存放了提示框的内容(一个纯文本字符串)
  • flow: 可选;这个属性允许我们控制如何显示提示框。我们可以支持很多方位,但是我们会覆盖4各常用方位: 上,左,右,下

现在,让我们为所有的提示框做好准备工作。步骤1-5的规则会应用到所有的提示框上,无论我们给 flow 属性什么值。步骤6-7对于不同的 flow 值会有所区分。

1. 相对性

这是用在提示框的父元素上的。让我们来给定一个定位指令,这样提示框的组成部分(即::before 和 ::after 伪元素)的绝对定位就可以以父元素做参照进行定位,而不是以整个页面或祖父元素或DOM树上方的其它外围元素作为参照进行定位。

[tooltip] {
position: relative;
}

2. 伪元素准备时间

是时候准备伪元素了。在这里,我们要对 ::before 和 ::after 设置常用属性。content 属性是真正让伪元素工作的属性,不过我们稍后再讨论它。

[tooltip]::before,
[tooltip]::after {
line-height: 1;
user-select: none;
pointer-events: none;
position: absolute;
display: none;
opacity: 0;

/* opinions */
text-transform: none;
font-size: .9em;
}

3. 丁克帽

我不知道丁克帽是不是说得通,我只是一直这么叫它。它是一个尖尖的小三角形,通过指向它的调用者,为提示框提供对话气泡的感觉。 注意到我们在边界颜色这一块,使用了 tranparent ;由于上色要根据提示框的 flow 值来,所以之后再加上颜色。

[tooltip]::before {
content: ‘’;
z-index: 1001;
border: 5px solid transparent;
}

content: ‘’;声明中的值是一个空字符串,这并不是笔误。字符串里面,我们不想要任何东西,但是我们需要这个属性,使得伪元素得以存在。 为了生成一个三角形,我们定义了一个实现边框,在空的盒子(没有内容)上加了一些厚度,而不设定盒子的宽度和高度,仅仅对盒子的每一条边都给一个边框颜色。

4. 气泡!

这里是重点了。注意到 content: attr(tooltip) 这一部分是说:“这个伪类应该使用 tooltip 属性的值作为这个伪类的内容。”这也是为什么使用属性而不是类名会这么赞的原因。

[tooltip]::after {
content: attr(tooltip); /* magic! */
z-index: 1000;

/* most of the rest of this is opinion */
font-family: Helvetica, sans-serif;
text-align: center;

/*
Let the content set the size of the tooltips
but this will also keep them from being obnoxious
*/
min-width: 3em;
max-width: 21em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

/* visible design of the tooltip bubbles */
padding: 1ch 1.5ch;
border-radius: .3ch;
box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35);
background: #333;
color: #fff;
}

注意看丁克帽和气泡的 z-index 值。这些值可以是任意的。但是要记住,z-index 值是相对的。 解释:一个z-index值为1001的元素,在一个z-index为3的元素内部。仅仅意味着,z-index: 3 容器内部,1001元素是最顶层的元素。 气泡的z-index应该至少比丁克帽的z-index低一档。如果它和丁克的一样高,或更高的话,如果你提示框使用了 box-shadow 的话,结果在丁克帽上回得到不一致的颜色效果。

5. 交互动作

我们的提示框是通过把鼠标移动到带提示框的元素上面,来激活的。差不多是这样。

[tooltip]:hover::before,
[tooltip]:hover::after {
display: block;
}

如果你回顾在第2不中的样式部分,你会看到我们对提示框的组成部分,使用了 opacity: 0; 以及 display: none; 。我们这么做是为了当提示框显示和隐藏时,可以使用CSS动画效果。 display属性是不能做成动画的,但是opacity属性可以!我们留到最后来处理动画的问题。如果你对动画提示框没兴趣,只要把第2步中的 opacity: 0; 删掉,无视第7步即可。 最后一件要应用到所有提示框上的是,如果提示框没有内容,能有一个方法来抑制提示框。如果你使用某种动态系统(Vue.js, Angular, 或者 React, PHP等等)来生成提示框的话,我们就不需要笨笨的空白气泡了!

/* don’t show empty tooltips */
[tooltip=’’]::before,
[tooltip=’’]::after {
display: none !important;
}

6. 流控制

这一步会变得更加复杂,因为我们会使用一些不那么常见的选择器,来帮助我们的提示框基于 flow 值(或没有flow属性)来确定位置。 在我们写样式之前,让我们看看将要用到一些选择器模式。

[tooltip]:not([flow])::before,
[tooltip][flow^=”up”]::before {
/* …
properties: values
… */
}

这是在告诉浏览器:“对于所有带有 tooltip 属性来说,其中没有 flow 属性的元素,或者有flow元素,但它的值是以’up’开头的:将这些样式套用到这类元素的::before伪元素上。” 我们在这里使用了一个模式,这样一来,这些东西可以扩展到其它流上,而步需要重复这么多的CSS。这个模式 flow^=”up” 使用了 ^= (开头)匹配符。 如果你想增加其它流控制的话,通过这个模式,也可以将样式应用在 up-right 和 up-left 方向上(代码中)。我们在这里不会讨论这些流控制,不过你可以在CodePen上,我原来的提示框演示中看到如何使用它们。 以下是教程中所讲到的4个流所对应的CSS代码块。 上(这是默认的方向)

/* ONLY the ::before */
[tooltip]:not([flow])::before,
[tooltip][flow^=”up”]::before {
bottom: 100%;
border-bottom-width: 0;
border-top-color: #333;
}

/* ONLY the ::after */
[tooltip]:not([flow])::after,
[tooltip][flow^=”up”]::after {
bottom: calc(100% + 5px);
}

/* Both ::before & ::after */
[tooltip]:not([flow])::before,
[tooltip]:not([flow])::after,
[tooltip][flow^=”up”]::before,
[tooltip][flow^=”up”]::after {
left: 50%;
transform: translate(-50%, -.5em);
}

下:

[tooltip][flow^=”down”]::before {
top: 100%;
border-top-width: 0;
border-bottom-color: #333;
}

[tooltip][flow^=”down”]::after {
top: calc(100% + 5px);
}

[tooltip][flow^=”down”]::before,
[tooltip][flow^=”down”]::after {
left: 50%;
transform: translate(-50%, .5em);
}

左:

[tooltip][flow^=”left”]::before {
top: 50%;
border-right-width: 0;
border-left-color: #333;
left: calc(0em - 5px);
transform: translate(-.5em, -50%);
}

[tooltip][flow^=”left”]::after {
top: 50%;
right: calc(100% + 5px);
transform: translate(-.5em, -50%);
}

右:

[tooltip][flow^=”right”]::before {
top: 50%;
border-left-width: 0;
border-right-color: #333;
right: calc(0em - 5px);
transform: translate(.5em, -50%);
}

[tooltip][flow^=”right”]::after {
top: 50%;
left: calc(100% + 5px);
transform: translate(.5em, -50%);
}

7. 让一切都动起来

动画是很神奇的。动画可以做到: 让用户感觉舒服 让用户感受到你的用户界面的空间感 注意到该看到的东西 让用户界面中本来非黑即白的生硬效果变得柔和 我们的提示框属于最后那一种。如果仅仅是让一个文字泡泡出现然后突然消失,效果是不令人满意的,我们可以让它更柔和一些。 关键帧 (@keyframes) 我们需要两个关键帧 (@keyframe) 动画。向上/向下提示框要用到tooltips-vert关键帧,而向左/向右提示框使用tooltips-horz关键帧。 注意,在这些关键帧中,我们只定义了提示框所需的终止状态。我们并不需要知道它们从何处来 (提示框本身就有状态信息)。我们只想控制它们要到哪儿去。

@keyframes tooltips-vert {
to {
opacity: .9;
transform: translate(-50%, 0);
}
}

@keyframes tooltips-horz {
to {
opacity: .9;
transform: translate(0, -50%);
}
}

现在,当一个用户的鼠标移到触发元素 (具有[tooltip]属性的元素) 上时,我们需要将这些关键帧应用到提示框上。因为我们采用了不同的流来控制提示框的显示方式,我们需要在样式中对它们进行定义。 使用:hover将控制传递给动画

[tooltip]:not([flow]):hover::before,
[tooltip]:not([flow]):hover::after,
[tooltip][flow^=”up”]:hover::before,
[tooltip][flow^=”up”]:hover::after,
[tooltip][flow^=”down”]:hover::before,
[tooltip][flow^=”down”]:hover::after {
animation:
tooltips-vert
300ms
ease-out
forwards;
}

[tooltip][flow^=”left”]:hover::before,
[tooltip][flow^=”left”]:hover::after,
[tooltip][flow^=”right”]:hover::before,
[tooltip][flow^=”right”]:hover::after {
animation:
tooltips-horz
300ms
ease-out
forwards;
}

我们不能对display属性进行动画,但是可以通过操作opacity属性,在提示框上加上淡入效果。我们也可以动画transform属性,它可以给提示框加上微妙的动作,触发的元素就像飞入某点的一样。 主要forward关键词在动画的声明中,这告诉动画当完成时不重置,而是继续停留在结束。

结论

棒极了!我们在这个教程里已经覆盖了很多,一堆提示框效果。 我们仅仅摸索了用css做提示框的表面。好好享受它们,继续试验,调制出你自己的方子!

多年来,变量是最常请求的CSS功能之一。变量可以更轻松地管理颜色,字体,大小和动画值,并确保它们在代码库中的一致性。 花了几年时间研究语法的细节,并决定变量如何适应控制级联和继承的现有规则。现在,它们以CSS“自定义属性”的形式供开发人员使用。 在本章中,我们将讨论CSS自定义属性的语法。我们来看看:

  • 如何定义属性并为这些属性设置默认值 级联和继承的规则如何与自定义属性一起使用 如何在媒体查询中使用自定义属性 最后,您应该很好地掌握如何在项目中使用自定义属性。

注意:浏览器对自定义变量的支持非常强大,存在于每个主要浏览器的最新版本中。但是,旧版但最近发布的浏览器版本仍然不支持这些版本,这些版本可能仍会被您网站的受众广泛使用。15之前的Microsoft Edge版本和9.1版之前的Safari版本完全缺乏支持。任何版本的Internet Explorer都是如此。Microsoft Edge 15有支持,但也有一些记录的错误。

定义自定义属性

要定义自定义属性,请选择一个名称,并使用两个连字符作为前缀。任何字母数字字符都可以是名称的一部分。也允许使用连字符(-)和下划线(_)字符。广泛的unicode字符可以是自定义属性名称的一部分,包括emojis。为了清晰和可读性,请坚持使用字母数字名称。 这是一个例子:

--primarycolor: #0ad0f9ff; /* Using #rrggbbaa color notation */

该–指示的CSS解析器,这是一个自定义属性。无论将属性用作变量,属性的值都将替换该属性。 自定义属性名称区分大小写。换句话说,–primaryColor并且–primarycolor被认为是两个不同的属性名称。这与传统的CSS背道而驰,其中财产和价值案例并不重要。但是,它与ECMAScript处理变量的方式一致。 与其他属性(如displayor)一样font,必须在声明块中定义CSS自定义属性。一种常见的模式是在规则:root集中定义使用psuedo-element作为选择器的自定义属性:

:root {
–primarycolor: #0ad0f9ff;
}

:root是一个伪元素,它引用文档的根元素。对于HTML文档,这是html元素。对于SVG文档,它是svg元素。通过使用:root,可以在整个文档中立即获得属性。

使用自定义属性

要将自定义属性值用作变量,我们需要使用该var()函数。例如,如果我们想将–primarycolor自定义属性用作背景颜色,我们将使用以下内容:

body {
background-color: var(–primarycolor);
}

我们的自定义属性的值将成为属性的计算值background-color。 到目前为止,自定义属性只能用作变量来设置标准CSS属性的值。例如,您不能将属性名称存储为变量,然后重复使用它。以下CSS不起作用:

:root {
–top-border: border-top; /* Can’t set a property as custom property’s value */
var(–top-border): 10px solid #bc84d8 /* Can’t use a variable as a property */
}

您也不能将属性 - 值对存储为变量并重用它。以下示例也无效:

:root {
–text-color: ‘color: orange’; /* Invalid property value */
}
body {
var(–text-color); /* Invalid use of a property */
}

最后,您也无法将变量连接为值字符串的一部分:

:root {
–base-font-size: 10;
}
body {
font: var(–base-font-size)px / 1.25 sans-serif; /* Invalid CSS syntax. */
}

自定义属性被设计为用作根据CSS规范解析的属性。如果浏览器供应商采用CSS扩展规范,我们有一天可能会使用自定义属性来创建自定义选择器组或自定义规则。但是,目前我们仅限于将它们用作变量来设置标准属性值。

设置后备值

该var()函数实际上最多接受两个参数。第一个参数应该是自定义属性名称。第二个参数是可选的,但应该是声明值。如果尚未定义自定义属性值,则此声明值将用作一种回退值。 我们来看下面的CSS:

.btn__call-to-action {
background: var(–accent-color, salmon);
}

如果–accent-color已定义 - 让我们说它的值是#f30- 然后具有.btn__call-to-actionclass属性的任何路径的填充颜色将具有红橙色填充。如果没有定义,填充将是鲑鱼。 声明值也可以嵌套。换句话说,您可以使用变量作为var函数的回退值:

body {
background-color: var(–books-bg, var(–arts-bg));
}

在上面的CSS中,如果–books-bg已定义,则背景颜色将设置为–books-bg属性的值。如果没有,背景颜色将改为分配给的任何值–arts-bg。如果这两个都没有定义,那么背景颜色将是属性的初始值 - 在这种情况下transparent。 当自定义属性被赋予对其所使用的属性无效的值时,会发生类似的事情。考虑以下CSS:

:root {
–footer-link-hover: #0cg; /* Not a valid color value. */
}
a:link {
color: blue;
}
a:hover {
color: red;
}
footer a:hover {
color: var(–footer-link-hover);
}

在这种情况下,–footer-link-hover属性的值不是有效颜色。在Microsoft Edge中,页脚链接的悬停状态颜色将从a:hover选择器继承。在大多数其他浏览器中,悬停状态颜色将从body元素的文本颜色继承。 自定义属性和级联 自定义属性也遵循级联规则。后续规则可以覆盖它们的值:

:root {
–text-color: #190736; /* navy */
}
body {
–text-color: #333; /* Dark gray */
}
body {
color: var(–text-color);
}

在上面的示例中,我们的正文将为深灰色。我们还可以基于每个选择器重置值。让我们为这个CSS添加更多规则:

:root {
–text-color: #190736; /* navy */
}
body {
–text-color: #333; /* Dark gray */
}
p {
–text-color: #f60; /* Orange */
}
body {
color: var(–text-color);
}
p {
color: var(–text-color)
}

在这种情况下,包含在p元素标记中的任何文本都将为橙色。但是内部div或其他元素中的文本仍然是深灰色的。 也可以使用style属性设置自定义属性的值- 例如,style=”–brand-color: #9a09af”- 这在基于组件的前端体系结构中很有用。

Transforms允许我们创建不可能的效果和交互。与过渡和动画结合使用时,我们可以创建旋转,dance和zoom的元素和界面。特别是三维变换使得模仿物体成为可能。在本文中,我们将介绍2D变换函数(此处介绍了3D函数)。 有四种主要的二维变换函数:rotate,scale,skew,和translate。其他六个函数让我们在一个维度上转换元素:scaleX和scaleY; skewX和skewY; 和translateX和translateY。

rotate()

旋转变换围绕该transform-origin点围绕指定的角度旋转元素。使用rotate()顺时针(正角度值)或逆时针(负角度值)倾斜元素。它的效果很像风车或风车,如下所示。 紫色框从其起始位置旋转了55度,如虚线所示 该rotate()函数接受角度单位的值。角度单位由CSS值和单位模块级别3定义。这些可以是deg(度),rad(弧度),grad(梯度)或turn单位。一个完整旋转等于360deg,6.28rad,400grad,或1turn。 超过一个旋转(例如,540deg或1.5turn)的旋转值将根据其剩余值进行渲染,除非设置为动画或过渡。换句话说,540deg渲染与180deg(540度减去360度)相同,并且渲染与(1.5-1)1.5turn相同.5turn。但是,从一个过渡或动画0deg来540deg或1turn以1.5turn将旋转元素一个和倍半。

2D缩放功能:scale,scaleX,和scaleY

使用缩放功能,我们可以在X维度(scaleX),Y维度(scaleY)或两者(scale)中增加或减少元素的渲染大小。缩放如下图所示,其中边框表示框的原始边界,+标记其中心点。 一个方框(左)缩放2倍(右) 每个比例函数都接受乘数或因子作为其参数。这个乘数可以是任何正数或负数。不支持百分比值。正乘数大于1增加元素的大小。例如,scale(1.5)将元素在X和Y方向上的尺寸增加1.5倍。之间的正乘数0和1将降低元素的大小。 小于的值0也会导致元素向上或向下缩放并创建反射(翻转)变换。 警告:使用scale(0)将导致元素消失,因为将数字乘以零会产生零。 使用scale(1)创建标识转换,这意味着它被绘制到屏幕,就像没有应用缩放转换一样。使用scale(-1)不会更改元素的绘制大小,但负值将导致元素被反射。即使元素没有出现变换,它仍会触发新的堆叠上下文并包含块。 可以使用该scale功能分别缩放X和Y尺寸。只需传递两个参数:scale(1.5, 2)。第一个参数缩放X维度; 第二个缩放Y维度。例如,我们可以单独使用X轴反射物体scale(-1, 1)。传递单个参数可以按相同因子缩放两个维度。

2D转换功能:translateX,translateY,和translate

平移元素会将其绘制位置与布局位置偏移指定的距离。如同其它变换,平移一个元件不改变其offsetLeft或offsetTop位置。但是,它会影响它在屏幕上的可视位置。 每个2D平移函数translateX- translateY,和 - translate接受参数的长度或百分比。长度单位包括像素(px), ,em,rem和视口单元(vw和vh)。 该translateX函数更改元素的水平渲染位置。如果元素位于左侧零像素处,则将transform: transitionX(50px)其渲染位置移动50个像素到其起始位置的右侧。同样,translateY更改元素的垂直渲染位置。transform: transitionY(50px)将元素垂直偏移50个像素的变换。 使用translate(),我们可以使用单个函数垂直和水平移动元素。它最多接受两个参数:X转换值和Y转换值。下图显示了一个元件与效果transform的值translate(120%, -50px),其中,所述左绿色正方形是在原来的位置,而右侧绿色正方形水平平移120%,并从它的含有元素(虚线框)垂直-50px。 具有转换值为translate的元素的效果(120%, - 50px) 传递单个参数translate相当于使用translateX; Y转换值将设置为0。使用translate()是更简洁的选择。申请translate(100px, 200px)相当于translateX(100px) translateY(200px)。 正转换值将元素向右移动(for translateX)或向下移动(for translateY)。负值将元素移动到左(translateX)或向上(translateY)。 翻译对于向左,向右,向上或向下移动项目特别有用。更新的值left,right,top,和bottom属性强制浏览器重新计算整个文档布局信息。但是在计算布局之后计算变换。它们影响其中的元素出现在屏幕上,而不是他们的实际尺寸。是的,将文档布局和渲染视为单独的概念是很奇怪的,但就浏览器而言,它们是。 转换属性可能会到达您附近的浏览器 在CSS的最新版本来变换规范增加translate,rotate以及scale 性能的CSS。变换属性的工作方式与其对应的变换函数非常相似,但值是以空格分隔的,而不是以逗号分隔的。例如,我们可以transform: rotate3d(1, 1, 1, 45deg)使用该rotate属性表达:rotate: 1 1 1 45deg。同样,translate: 15% 10% 300px在视觉上与之相同transform: translate3d(15%, 10%, 300px)并且scale: 1.5 1.5 3相同transform: scale3d(1.5, 1.5, 3)。通过这些属性,我们可以与其他转换分开管理旋转,平移或缩放转换。 在撰写本文时,浏览器对转换属性的支持仍然非常稀少。Chrome和三星互联网支持开箱即用。在Firefox 60及更高版本中,支持隐藏在标志后面; 访问about: config并设置layout.css.individual-transform.enabled为true。

skew,skewX和skewY

歪斜变换会使点之间的角度和距离发生偏移,同时将它们保持在同一平面内。歪斜变换也称为剪切变换,它们会扭曲元素的形状,如下所示,其中虚线表示元素的原始边界框。 矩形沿其X维度倾斜45度 偏斜函数skew- skewX,和 - skewY接受大多数角度单位作为参数。度,渐变和弧度是倾斜函数的有效角度单位,而转弯单位可能显然不是。 该skewX功能在X或水平方向上剪切元素(参见下面的图像)。它接受一个参数,该参数也必须是一个角度单位。正值将元素向左移动,负值将元素向右移动。 左图像未被变换,而右图像显示变换的效果:skewX(30deg) 同样,skewY剪切Y或垂直方向的元素。下图显示了效果transform: skewY(30deg)。原点右侧的点向下移动,带有正值。负值将这些点向上移动。 同样,左图像保持未变换,右图像垂直偏斜30度 这带给我们的skew功能。该skew函数需要一个参数,但最多可接受两个参数。第一个参数在X方向上扭曲一个元素,第二个参数在Y方向上扭曲它。如果只提供一个参数,则假定第二个值为零,使其相当于单独在X方向上的倾斜。换句话说,skew(45deg)渲染相同skewX(45deg)。

当前变换矩阵

到目前为止,我们已经分别讨论了变换函数,但它们也可以组合在一起。想要缩放和旋转对象?没问题:使用转换列表。例如:

.rotatescale {
transform: rotate(45deg) scale(2);
}

这将产生您在下面看到的结果。 应用原始元素(左)和组合旋转和缩放变换后(右) 使用转换函数时,顺序很重要。这是一个比谈论更好的一点,所以让我们看一个例子来说明。以下CSS倾斜并旋转元素:

.transformEl {
transform: skew(10deg, 15deg) rotate(45deg);
}

它为我们提供了您在下面看到的结果。 歪斜变换后的元素(10度,15度)旋转(45度) 如果首先旋转元素然后将其倾斜会发生什么?

.transformEl {
transform: rotate(45deg) skew(10deg, 15deg);
}

如下所示,效果完全不同。 旋转后然后倾斜的元素 这些变换中的每一个具有由其变换函数的顺序创建的不同的当前变换矩阵。为了完全理解为什么这样,我们需要学习一点矩阵乘法。这也有助于我们理解matrix和matrix3d功能。 矩阵乘法和矩阵函数 甲矩阵是排列成行和列的矩形号码或表达式的阵列。所有变换可以使用4×4矩阵表示,如下所示。 用于3D变换的4×4矩阵 该矩阵对应于matrix3d函数,该函数接受16个参数,每个参数用于4×4矩阵的每个值。二维变换也可以使用3×3矩阵表示,如下所示。 用于2D变换的3×3矩阵 该3×3矩阵对应于matrix变换函数。该matrix()函数接受六个参数,每个参数对应于值a到f。 可以使用矩阵和/ matrix或matrix3d函数来描述每个变换函数。下图显示了scale3d函数的4×4矩阵,其中sx,sy和sz分别是X,Y和Z维度的缩放因子。 当我们组合变换时 - 例如transform: scale(2) translate(30px, 50px)- 浏览器将每个函数的矩阵相乘以创建新矩阵。这个新矩阵是应用于元素的。 但这是关于矩阵乘法的事情:它不是可交换的。使用简单值,3×2的乘积与2×3相同。但是,对于矩阵,A × B的乘积不一定与B × A的乘积相同。我们来看一个例子。我们将计算出的矩阵乘积transform: scale(2) translate(30px, 50px)。 我们的元素按比例缩放了两倍,然后水平翻译60像素,垂直翻译100像素。我们也可以使用以下matrix功能表达此产品:transform: matrix(2, 0, 0, 2, 60, 100)。现在让我们切换这些变换的顺序 - 即transform: translate(30px, 50px) scale(2)。结果如下所示。 用于平移(30px,50px)和刻度(2)的矩阵的乘积 请注意,我们的对象仍然按比例缩放了两倍,但在这里它被水平翻译了30个像素而垂直翻译了50个像素。使用该matrix函数表示,这是transform: matrix(2, 0, 0, 2, 30, 50)。 值得注意的是,继承转换的功能与转换列表类似。每个子变换乘以应用于其父变换的任何变换。例如,请使用以下代码:

这与以下内容相同:

p在两种情况下,元素的当前变换矩阵都是相同的。虽然到目前为止我们专注于2D变换,但上述内容也适用于3D变换。第三个维度增加了深度的幻觉。它还以新功能和属性的形式带来了一些额外的复杂性。

随着Web平台的发展,它也获得了类似原生应用的特点。其中一个功能是CSS Scroll Snap Module。Scroll snap允许开发人员定义界面在滚动操作期间应该移动的距离。您可以使用它来构建幻灯片放映或分页界面 - 目前需要JavaScript和DOM操作才能完成。 Scroll snap作为一项功能经历了很多变化。早期的2013版规范 - 当时称为Scroll Snap Points–定义了一种基于坐标和像素的方法来指定滚动距离。此版本的规范是在Microsoft Edge,Internet Explorer 11和Firefox中实现的。 Chrome 69+和Safari 11+实现了规范的最新版本,该版本使用了盒子对齐模型。这就是我们将在本节中关注的内容。 警告: 当前浮动在Web上的许多滚动快照教程都基于早期的CSS Scroll Snap Points规范。标题中“points”一词的出现是教程可能依赖于旧规范的一个标志。然而,更可靠的指标是存在scroll-snap-points-x或scroll-snap-points-y属性。 由于滚动捕捉非常适合幻灯片放映布局,这就是我们要构建的内容。这是我们的标记。

这就是我们所需要的一切。我们不需要具有外部包裹元件和内部滑动容器。我们也不需要任何JavaScript。 现在我们的CSS:

* {
box-sizing: border-box;
}

html, body {
padding: 0;
margin: 0;
}

.slideshow {
scroll-snap-type: x mandatory; /* Indicates scroll axis and behavior */
overflow-x: auto; /* Should be either `scroll` or `auto` */
display: flex;
height: 100vh;
}

.slideshow img {
width: 100vw;
height: 100vh;
scroll-snap-align: center;
}

添加scroll-snap-type以.slideshow创建滚动容器。此属性的值x mandatory描述了我们要滚动的方向以及滚动快照严格性。在这种情况下,该mandatory值告诉浏览器,当没有活动的滚动操作时,它必须捕捉到捕捉位置。使用display: flex只是确保我们的所有图像水平堆叠。 现在我们需要的另一个属性是scroll-snap-align。此属性指示如何在滚动容器的捕捉端口内对齐每个图像的滚动捕捉区域。它接受三个值:start,end,和center。在这种情况下,我们使用了center这意味着每个图像将在视口中居中,如下所示。