如何不用一行 JS 代码做一个现代化可交互网站

2,106 阅读7分钟

之前做了一个纯 HTML+CSS 网站,完全没有 JS 代码,大家可以点击这个链接体验一下 woopen.github.io/ssp/

在网站源码中完全搜索不到 script 标签。

你还可以在 Github 上面找到相关源码 github.com/woopen/ssp

这篇文章就来非常详细的分析这个网站是如何制作的,它是如何实现交互、验证和模态框的,相信看完这篇文章可以学到很多不为人知的 CSS 小技巧。

导航栏

首先从网站最前面的导航栏开始,如下图所示。

导航栏默认是收起的,点击可以展开,效果如下图所示。

可以看到点击这个导航栏按钮,按钮上的 3 条杠会变成一个关闭图形,并且有一个很酷的导航展开动画,从导航按钮开始展开一个圆,然后出现导航菜单,鼠标放到导航菜单项上面还有很炫的 Hover 效果,最后再次点击导航按钮,展开的圆形会被收起,按钮的关闭图形也渐变成 3 条杠。

要知道这整个导航栏效果是完全没有一行 JS 代码的,完全只使用 HTML+CSS 来实现。接下来让我们看一看它是如何被实现的。

首先来看一下整个导航栏的 HTML 代码,如下所示。

可以发现 HTML 中首先有一个神秘的 input 元素。接着是导航按钮,按钮里面有一个 icon。再往下是导航栏的背景,也就是点击展开的那个圆。最后是导航的菜单项。

点击交互

首先来看一下大家最关心的,类似于 JS 的 onclick 导航点击效果是如何实现的,在 CSS 中的 checkbox 元素是有 CSS 状态的,就和 :hover 类似,如果一个 checkbox 被选中,那么它的 :checked CSS 状态就会被激活。

input:checked {
    background: red;
}

上面代码中,如果 checkbox 元素被选中,背景就会变成红色,取消选择则背景色消失。

在浏览器中我们点击 checkbox 元素,可以选中或取消选中它,交互功能是有了,但是在 UI 上我们需要的是一个按钮,并不是选中框,这里的利用 label 元素的 for 属性,label 元素的 for 属性可以关联到 checkboxid 属性,这样点击 label 元素就相当于点击到了 checkbox 元素,利用这一点,就可以将 checkbox 元素隐藏,只展示 label 元素。

可以发现导航栏的 HTML 中的神秘的 input 元素,就是现在说的这个 checkbox 元素,它下面的导航按钮就是这个 label 元素。这样就实现了点击交互的功能。

导航按钮

导航按钮里面那个 icon 是使用纯 CSS 来实现的,相关 SASS 代码如下。

&__icon {
    position: relative;
    margin-top: 3.5rem;

    &,
    &::before,
    &::after {
      display: inline-block;
      width: 3rem;
      height: 2px;
      background-color: $color-grey-7;
    }

    &::before,
    &::after {
      content: '';
      position: absolute;
      left: 0;
      transition: all 0.2s;
    }

    &::before {
      top: -0.8rem;
    }
    &::after {
      top: 0.8rem;
    }
}

上面代码中开始的 & 表示 .navigation,3 条杠 icon 是利用 css 的 beforeafter 伪元素来实现的,加上自己本身的 1 条杠,刚好 3 条杠。

当鼠标 hover 这个按钮时,上下两个横杠会展开,相关代码如下。

&__button:hover &__icon::before {
    top: -1rem;
}
&__button:hover &__icon::after {
    top: 1rem;
}

当点击这个按钮时,3 条杠的 icon 会变成一个关闭 icon。

&__checkbox:checked + &__button &__icon {
    background-color: transparent;
}
&__checkbox:checked + &__button &__icon::before {
    top: 0;
    transform: rotate(135deg);
}
&__checkbox:checked + &__button &__icon::after {
    top: 0;
    transform: rotate(-135deg);
}

这里其实就是利用上面介绍到的 checkbox 元素的 :checked 状态,利用 + 选择符,可以选择到它的下一个兄弟节点,也就是导航按钮,接着把中间一条钢隐藏,再把上下两条杠旋转一下就是一个关闭 icon 了。

背景

导航背景就是点击按钮展开的那个圆形背景,它的样式代码如下。

  &__background {
    position: fixed;
    top: 6.5rem;
    right: 6.5rem;
    height: 6rem;
    width: 6rem;
    border-radius: 50%;
    background-image: radial-gradient(
      $color-primary-light,
      $color-primary-dark
    );
    z-index: 1000;
    transition: transform 0.8s cubic-bezier(0.86, 0, 0.07, 1);
  }

这个圆形背景默认只是一个小圆形,被导航按钮覆盖(按钮 z-index 为 2000),当点击按钮时,也就是 checkbox 被选中时,这个小圆形会被放大 80 倍,这样我们就可以看到背景展开的效果,相关代码如下。

&__checkbox:checked ~ &__background {
    transform: scale(80);
}

同样是利用 :checked 伪类,并且搭配 ~ 一般兄弟组合器选择到背景元素。

菜单项

整个菜单项的样式如下所示,它默认是被隐藏的。

&__nav {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    opacity: 0;
    visibility: hidden;
    height: 100vh;
    z-index: 1500;
    transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

它下面的菜单连接样式如下所示。

&__link {
    &:link,
    &:visited {
      background-image: linear-gradient(
        120deg,
        transparent 0%,
        transparent 50%,
        $color-white 50%
      );
      background-size: 220%;
      transition: all 0.4s;

      span {
        display: inline-block;
        margin-right: 1.5rem;
      }
    }
}

由于相关代码较多,上面代码中只展示了部分关键样式。链接按钮的背景色是一个渐变色,从透明渐变到白色,并且大小是父级的两倍,这样就把白色部分隐藏掉了,只显示透明部分。

可能从上面代码不好想到这样效果,下面把 background-size 样式去除,并把透明色变成黑色。

background-image: linear-gradient(120deg, #000 0%, #000 50%, #fff 50%);

效果过如下所示。

利用 background-size 增大背景色宽度,从而隐藏白色部分,然后在 :hover 时,移动背景色的位置,从透明移动到白色,这样就实现了比较酷的 :hover 效果,相关代码如下所示。

&:hover,
&:active {
  background-position: 100%;
  color: $color-primary;
  transform: translateX(1rem);
}

当然和背景小节一样,同样是利用 :checked 伪类,并且搭配 ~ 一般兄弟组合器显示菜单项。

&__checkbox:checked ~ &__nav {
    width: 100%;
    opacity: 1;
    visibility: visible;
}

图片轮廓

接下来看一看图片轮廓是如何实现的,鼠标放在图片上面,当前图片会被放大,其他图片会被缩小,并且被放大的元素的会显示在最前面并且有一个镂空的边框。效果如下图所示。

:hover 时当前图片放大,其他缩小相关样式代码如下所示。

&:hover &-img:not(:hover) {
    transform: scale(0.9);
}
&:hover {
    transform: scale(1.05) translateY(-0.5rem);
    box-shadow: 0 2.5rem 4rem rgba($color-black, 0.5);
    z-index: 20;
} 

这里主要是利用 :not 这个否定伪类来实现,:not(:hover) 就表示没有被 :hover 时的效果。

:hover 时出现的边框是利用 outlineoutline-offset 这两个样式来实现,相关代码如下所示。

&-img {
    width: 50%;
    position: absolute;
    border-radius: 2px;
    box-shadow: 0 1.5rem 4rem rgba($color-black, 0.4);
    z-index: 10;
    outline-offset: 2rem;
    transition: all 0.2s;
}
&:hover {
    outline: 1.5rem solid $color-primary;
    transform: scale(1.05) translateY(-0.5rem);
    box-shadow: 0 2.5rem 4rem rgba($color-black, 0.5);
    z-index: 20;
}

outlineborder 类似,但是它不会占空间,outline-offset 可以对 outline 做偏移,两个加起来就实现了上图中的效果。

模态框

再往下是购买产品部分,首先 :hover 卡片会有一个 3D 旋转效果,点击预订按钮会弹出一个详情模态框,点击关闭按钮可以正常关闭,效果如下图所示。

3D 翻转

首先来看一下卡片的 3D 翻转效果是如何实现的。

上图是卡片的 HTML 的代码,可以看到一个卡片是分为正面和背面的。

.card {
  position: relative;
  height: 52rem;
  margin: 0 1rem;
  perspective: 150rem;
  cursor: pointer;

  &:hover &__side--front {
    transform: rotateY(-180deg);
  }

  &:hover &__side--back {
    transform: rotateY(0);
  }

  &__side {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    overflow: hidden;
    border-radius: 3px;
    box-shadow: 0 1.5rem 4rem rgba($color-black, 0.15);
    transition: all 0.8s ease;
    backface-visibility: hidden;

    &--front {
      background-color: $color-white;
    }

    &--back {
      transform: rotateY(180deg);
    }
  }
}

模态框交互

接下来看一下如何只用 HTML+CSS 实现模态框弹出和关闭效果,而且弹出和关闭都有个比较舒服的动画。

可能有小伙伴已经开始抢答了,肯定是利用的 checkbox:checked 来实现这个效果的,就和导航那个一样。恭喜抢答的小伙伴,回答完全错误。。

没有使用 :checked 来实现是因为这里有 3 个卡片,每一个卡片的按钮都可以打开模态框,但是只有一个模态框它们打开的是同一个,所以模态框。不能和卡片按钮同级,需要放到外面,但是 CSS 中是 没有父级选择器 的,也就是我们不能和导航那里一样用相邻兄弟选择器之类的选到模态框。

这里用的是另一个 CSS 小技巧。打开模态框可以发现网页地址变了,hash 变成了 popup,关闭又变回去了,如下图所示。

模态框的 HTML 代码如下所示。

可以看到它的 ID 和 URL 上的 hash 是对的上的。这里的模态框交互就是利用的这个 ID,CSS 中有个 :target 伪类,表示当元素的 ID 与 URL 的 hash 相等时激活。

而预订按钮其实是一个 a 元素,它的 href#popup 与模态框 ID 匹配,模态框样式代码如下所示。

.popup {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba($color-black, 0.8);
  z-index: 3000;
  opacity: 0;
  visibility: hidden;
  transition: all 0.3s;

  &:target {
    opacity: 1;
    visibility: visible;
  }

  &:target &__content {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

可以看到默认是隐藏的,:target 下才显示。模态框的关闭按钮也是一个 a 标签,这样就可以关闭效果。

背景视频和错切

再往下是用户故事部分,如下图所示。

它的 HTML 如下所示。

这个部分背景是动态的,它是一个循环播放的视频,实现比较简单,代码如下所示。

.bg-video {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
  opacity: 0.15;
  overflow: hidden;

  &__content {
    height: 100%;
    width: 100%;
    object-fit: cover;
  }
}

用户故事部分中间是一个故事卡,可以看到它有一个倾斜的效果,这个是用 transform: skewX() 来实现的,相关代码如下所示。

.story {
  width: 60%;
  margin: 0 auto;
  padding: 6rem;
  padding-left: 9rem;
  border-radius: 3px;
  transform: skewX(-12deg);
  box-shadow: 0 3rem 6rem rgba($color-black, 0.1);
  background-color: rgba($color-white, 0.6);

  &__shape {
    position: relative;
    width: 15rem;
    height: 15rem;
    float: left;
    border-radius: 50%;
    transform: translateX(-3rem) skewX(12deg);
    overflow: hidden;
    clip-path: circle(50% at 50% 50%);
    shape-outside: circle(50% at 50% 50%);
  }
}

之前我的 “CSS3 transform 和 canvas 背后不为人知的秘密” 这篇文章讲解了 CSS3 的 transform: skew() 这条样式背后的实现原理,感兴趣的小伙伴可以去看一看。

还可以在上面代码中发现一条 shape-outside: circle(50% at 50% 50%) 的样式,这个样式可以给元素设置形状,在这里的主要作用,是让问题和图片 UI 更加自然,如果我们去掉这条样式卡片将会像下图这样。

但是加上这条样式,文字就可以环绕这个圆形的图片了。

表单

网站的最后是一个表单部分,这个表单利用 HTML+CSS 实现了表单验证效果,如果没有填写必填字段,将会出现警告 UI,效果见下图。

表单相关 HTML 代码如下所示。

表单验证用到了 H5 表单验证功能,这里是对于必填项添加了 required 属性,另外在对于没有通过验证的项目会出现错误 UI 提示,输入框 UI 代码如下。

&__input {
    display: block;
    width: 90%;
    padding: 1.5rem 2rem;
    font-size: 1.5rem;
    font-family: inherit;
    color: inherit;
    border-radius: 2px;
    background-color: rgba($color-white, 0.5);
    border: none;
    border-bottom: 3px solid transparent;
    transition: all 0.3s;

    &:focus {
      outline: none;
      box-shadow: 0 1rem 2rem rgba($color-black, 0.1);
      border-color: $color-primary;
    }

    &:focus:invalid {
      border-color: $color-secondary-dark;
    }

    &::-webkit-input-placeholder {
      color: $color-grey-6;
    }
}

这里主要是利用 :invalid 伪类,它会在验证不通过时激活。

另一个比较酷的效果是,用户在输入文字是,输入框的提示会下滑到输入框下面,相关代码如下所示。

&__label {
    display: block;
    margin-top: 0.7rem;
    margin-left: 2rem;
    font-size: 1.2rem;
    font-weight: 700;
    transition: all 0.3s;
}
&__input:placeholder-shown + &__label {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4rem);
}

这个 label 提示,默认是隐藏在输入框上的,在用户输入文字时会下滑并展示出来。这里主要是用到 :placeholder-shown 伪类,它在输入框的 placeholder 文字在显示时激活。

总结

这篇文章介绍了如何不使用 JS 的情况下来实现一些交互效果,这里主要是使用的 checkbox:checked 伪类来实现,除此之外还介绍了各种炫酷的 UI 效果的实现思路。为了简洁文章只展示了实现的关键代码,并且有部分功能没有展示,可以点击下面链接查看完整源代码。

更多

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿