普通视图

发现新文章,点击刷新页面。
昨天 — 2025年1月17日首页

前端逻辑属性调整排版方向 write-mode

2025年1月17日 22:33

我们平时的 text 文本基本上都是横向布局的,但是有些诗歌文艺范的内容,很多文本都是垂直布局的,那么我们怎么布局呢, css 中有个属性 write-mode 可以

writing-mode: horizontal-tb; //默认水平方向布局
writing-mode: vertical-lr; //垂直方向布局,从做往右分列延伸
writing-mode: vertical-rl; //垂直方向布局,从右往左分列延伸

效果就像下面那样 MDN-write-mode

image.png

前端小知识包含块

2025年1月17日 22:33

我们以前应用布局的时候,经常会参考父元素,因此写文章很多基本上也会提到相对于父元素,实际上很多我们严重的父元素,其更精确的概念应该是包含块,因为单纯的父元素他不精确,有些是浮动元素或者定位元素,就不是明显的父子关系了

包含块确定规则

  • 常规元素和浮动元素父元素的内容盒
  • 绝对定位 absolute: 第一个定位祖先的填充盒(padding + content)
  • 固定定位 fixed:
    • 无变形祖先-往上找找不到使用了 transform 的父元素,其就是包含块就是视口
    • 有变形祖先-包含块有的祖先使用了 transform,就是变形祖先,包含块就是变形祖先

ps:看了fixed 这些是不是感觉天塌了,以前学的定位不太准了,实际上不是的,只是我们接触到得更深了,也能避免更多问题了,下面的 fixed 变形祖先实际上平时根本接触不到,因此按照以前总结也没啥问题,就当牛顿和爱因斯坦理论在合适的地方使用就好了,碰到了我们也知道咋回事就行了

我么更新一下我们的知识点,父元素换成包含块就正确了,否则全是不准确的哈

  • 元素的 width 百分比相对的是 父元素 包含块 宽度
  • 元素的 height 百分比相对的是 父元素 包含块 高度
  • 元素的 margin 百分比相对的是 父元素 包含块 宽度
  • 元素的 padding 百分比相对的是 父元素 包含块 宽度
  • 元素的 left 百分比相对的是 父元素 包含块 左边缘
  • 元素的 top 百分比相对的是 父元素 包含块 右边缘

CSS transform 动画与 z-index 层叠上下文分析

作者 choreau
2025年1月17日 00:28

问题表现

CSS 动画很常见,如果动画时刚好有元素叠加在动画之上,就不太常见了。比如,点击一个元素会有动画,同时页面有一个 toast 提示,刚好 toast 的位置叠加在动画的元素之上。按说也没有问题,toast 的层级更高,在上面显示。

但是在 iOS WebView 中,会表现异常,比如:

<template>
  <div class="page relative h-screen w-screen">
    <div class="main absolute">
      <button
        type="button"
        class="btn animate__animated absolute text-white"
        @click="handleClick($event)"
      >
        动画元素
      </button>
      <div class="toast absolute text-white">
        toast 提示提示提示提示提示提示提示
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { showToast } from 'vant'

function handleClick(event: TouchEvent | MouseEvent): void {
  const target = event.currentTarget as HTMLElement
  showToast({
    message: '测试toast提示重叠在动画元素之上',
    duration: 2000,
  })

  target.classList.add('animate__headShake')
  target.addEventListener(
    'animationend',
    () => {
      target.classList.remove('animate__headShake')
    },
    { once: true },
  )
}
</script>

<style lang="scss" scoped>
.main {
  box-sizing: content-box;
  border: 1px solid blue;
  left: 50%;
  top: calc(50% + 0px);
  margin: -300px 0 0 -300px;
  width: 600px;
  height: 600px;
}
.btn {
  background: red;
  left: 50%;
  top: 50%;
  margin: -150px 0 0 -150px;
  width: 300px;
  height: 300px;
}
.toast {
  border: 1px solid yellow;
  left: 0;
  bottom: 150px;
  padding: 0 20px;
  width: 100%;
  height: 100px;
  line-height: 100px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.6);
  font-size: 40px;
  z-index: 2002;
}
</style>

点击元素时给元素添加动画,同时页面显示 toast,动画 headShake 实现如下,就是 animation transform translate 等。当动画元素向左边偏移时,元素左边部分与 toast 重叠的部分会显示在最上层,当元素向右边偏移时,元素右边部分与 toast 重叠的部分会显示在最上层。这样在动画的过程中,图层交错显示,出现闪烁的现象。而期望的是 toast 一直显示在最上层。

@keyframes headShake {
    0% {
        -webkit-transform: translateX(0);
        transform: translateX(0)
    }

    6.5% {
        -webkit-transform: translateX(-6px) rotateY(-9deg);
        transform: translateX(-6px) rotateY(-9deg)
    }

    18.5% {
        -webkit-transform: translateX(5px) rotateY(7deg);
        transform: translateX(5px) rotateY(7deg)
    }

    31.5% {
        -webkit-transform: translateX(-3px) rotateY(-5deg);
        transform: translateX(-3px) rotateY(-5deg)
    }

    43.5% {
        -webkit-transform: translateX(2px) rotateY(3deg);
        transform: translateX(2px) rotateY(3deg)
    }

    50% {
        -webkit-transform: translateX(0);
        transform: translateX(0)
    }
}

描述不能清楚说明问题,可以看视频:

视频上传不了,视频截图:

image.png

可以看到,动画的过程中,它的一部分出现在了最上层。

这个问题,在 iOS WebView 中出现,在其他平台正常,另外在 iOS Safari 中也正常。

网上搜了一下,没有找到类似的案例。

为了说明 vant 的 showToast 没有干扰因素,代码中做了一个 toast 提示进行对比。

原因分析

  • 将 toast 的层级提升:vant toast 的 z-index 为 2002,设置更大的值。
  • 使用 will-change 明确指定渲染层:尝试给动画元素添加 will-change: transform; 确保动画在一个独立的合成层渲染。
  • 让 toast 也进行合成:给 toast 元素添加 transform: translate3d(0, 0, 0) 强制提升到独立渲染层。

尝试了以上方法都不能修复问题,后面就会发现,这个问题和层叠上下文有关,所以第一个方法没有意义,动画元素本来就是层叠上下文,所以应该已经有了独立的合成层。

问题是 iOS 是如何提升渲染合成层的?是如何进行渲染的?通过观察能够发现,动画元素和 toast 的重叠的一部分显示在最上层,而不是整体,所以渲染的时候是将动画的一部分进行了提升?

层叠上下文

MDN 介绍:

image.png

文章介绍的很详细,尤其是示例,清楚的展示了各个元素创建的独自的层叠上下文如何相互影响。一个层叠上下文之内只在同层级之间比较(上下层叠),它(父级)会作为一个整体,和它同级的层叠上下文比较。子级层叠上下文的 z-index 值只在父级中才有意义。

可以看到 transform 会形成层叠上下文。

另外,transform 也会影响 position 定位:

image.png

这个,我想不出来有什么使用场景需要这样。

形成层叠上下文 1

如果给 main 增加 z-index,形成层叠上下文,在动画时,设置动画元素 z-index: -1; 就可以实现 vant showToast 正常显示,但是 toast 还是不正常,会有被动画元素覆盖的现象。

<template>
  <div class="page relative h-screen w-screen">
    <div class="main absolute z-10">
      <button
        type="button"
        :class="btn animate__animated absolute text-white"
        @click="handleClick($event)"
      >
        动画元素
      </button>
      <div class="toast absolute text-white">
        toast 提示提示提示提示提示提示提示
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { showToast } from 'vant'

function handleClick(event: TouchEvent | MouseEvent): void {
  const target = event.currentTarget as HTMLElement
  showToast({
    message: '测试toast提示重叠在动画元素之上',
    duration: 2000,
  })

  target.classList.add('animate__headShake')
  target.style.zIndex = '-1'
  target.addEventListener(
    'animationend',
    () => {
      target.classList.remove('animate__headShake')
      target.style.zIndex = 'auto'
    },
    { once: true },
  )
}
</script>

效果:

image.png

形成层叠上下文 2

如果给 main 增加外层元素 animate__animated animate__fadeIn,即使 main 不设置 z-index,也能实现和上面相同的效果。神奇吧?

<template>
  <div class="page relative h-screen w-screen">
    <section class="animate__animated animate__fadeIn">
      <div class="main absolute">
        <button
          type="button"
          :class="btn animate__animated absolute text-white"
          @click="handleClick($event)"
        >
          动画元素
        </button>
        <div class="toast absolute text-white">
          toast 提示提示提示提示提示提示提示
        </div>
      </div>
    </section>
  </div>
</template>

因为 fadeIn 改变了 opacity 的原因,形成了层叠上下文:

@keyframes fadeIn {
    0% {
        opacity: 0
    }

    to {
        opacity: 1
    }
}

让 toast 也能正常显示

想要 toast 提示正常,需要将 toast 元素提到外层,这样它就属于外层的层叠上下文,不受 main 的层叠上下文的影响。

<template>
  <div class="page relative h-screen w-screen">
    <div class="main absolute z-10">
      <button
        type="button"
        :class="btn animate__animated absolute text-white"
        @click="handleClick($event)"
      >
        动画元素
      </button>
    </div>
    <div class="toast absolute text-white">
      toast 提示提示提示提示提示提示提示
    </div>
  </div>
</template>

我们再调整 toast 的定位,使它在动画元素的正上方,可以看到重合时已经不会被覆盖。

还有一种异常显示

如果上面形成层叠上下文中,设置了 absolute 定位,但是没有设置 z-index,那就没有形成层叠上下文,会产生这种效果,动画元素的一半消失,因为动画时设置层级为 -1,比背景图更低。

image.png

层叠上下文总结

如果上面动画时不把 z-index 的值设置为 -1,而是其他值比如 1,则修复不能生效。

这是为什么呢?通过以上尝试可以看到,要将 button 放入一个层叠上下文中,同时在 button 动画时(已经形成层叠上下文)设置 z-index,降低在 Z 轴的层级,但即使这样降低,它在动画时的渲染仍然遮挡了其他元素(toast),所以似乎 iOS 在处理合成层时有独特的逻辑,会突破层叠上下文的限制。

测试总结

开始以为是 iOS WebView 才有问题,跟 WebView 处理合成层渲染上的实现有关,后来经过测试发现:

  • iPhone 14 没有问题,Safari 和 WebView 均正常。
  • iPhone 12、11 的 Safari、WebView 在以上各版本的层叠上下文处理上,表现各不相同。

只能说,CSS 的层级问题十分复杂,不同版本的 iOS 和设备可能采用了不同的 GPU 渲染优化策略。但是我们能做的是利用层叠上下文,根据层叠上下文设置动画层级。

参考

昨天以前首页

CSS 代码过滤器:设计工具代码提取利器

2025年1月16日 15:59

🌟 诞生背景

在前端开发工作中,我们经常遇到这样的场景:

  1. 从 Figma、摹客、蓝湖 等设计工具中复制样式代码
  2. 需要从冗长的 CSS 中提取特定属性(如布局、字体、背景 等)
  3. 手动筛选过程繁琐且容易遗漏

为了解决这个效率问题,我用 Cursor 开发了 CSS Code Filter 浏览器插件,让代码提取变得简单高效。

插件信息

插件名字:CSS 代码过滤器

插件地址:chromewebstore.google.com/detail/css-…

效果演示动图

功能演示.gif

🚀 核心功能

1. 智能属性搜索

  • 支持模糊搜索和缩写匹配(如 w -> widthbg -> background
  • 实时过滤显示匹配的 CSS 属性
  • 支持多属性同时选择

2. 方案管理

  • 支持创建、保存、更新和删除常用的属性组合方案
  • 方案数据本地持久化存储
  • 快速切换不同的属性组合

3. 便捷操作

  • 一键复制过滤后的代码
  • 智能的属性选择界面
  • 清晰的已选属性展示

📝 使用方法

  1. 创建属性方案

    • 点击"新增方案"(例如:创建"布局方案"包含 width、height、margin 等属性)
    • 输入方案名称
    • 选择需要的 CSS 属性
    • 保存方案
  2. 提取 CSS 代码

    • 从设计工具复制 CSS 代码
    • 选择已有方案或直接选择需要的属性
    • 点击"过滤 CSS 代码"
    • 获取过滤后的结果并使用

🤝 支持作者

如果你觉得这个工具对你有帮助,欢迎:

🎉 结语

CSS Code Filter 源于实际开发中与设计工具协作的需求,致力于提升前端开发效率。它能让你的样式代码处理工作变得更加轻松。无论你是刚开始接触前端还是经验丰富的开发者,相信这个工具都能为你的工作带来便利。

欢迎下载使用,也期待你的反馈和建议!


超实用!HTML&CSS&JS 下拉多选功能解析,建议收藏

作者 前端Hardy
2025年1月16日 15:30

在前端开发过程中,我们常常会遇到一些看似常规却又充满挑战的需求。最近我就接到了一个任务,要求在页面中实现一个下拉选择支持多选的功能。起初,我考虑使用原生的 select 标签,但在实际操作中发现其默认的 UI 无法满足项目的设计需求,因此决定使用原生 JavaScript 来打造这个功能。以下是我整个开发过程的详细记录。

一、需求分析

项目需要一个能够让用户从多个选项中选择多个变量的下拉菜单。用户选择的变量需要实时显示在输入框中,并且在页面刷新后,用户之前的选择能够被保留。这意味着我们不仅要实现多选的交互逻辑,还要处理数据的存储和读取。

二、HTML 结构搭建

首先,我创建了一个基本的 HTML 结构。使用一个 div 容器作为整个下拉选择组件的外壳,并赋予其 container 类,用于设置样式和布局。在容器内部,有一个标题 h1 用于描述该组件的功能。 接着,创建了一个 select-container 类的 div 作为下拉选择的容器。在这个容器中,select-input 类的 div 用于显示当前选择的变量或提示信息,并且包含一个 input 元素用于显示内容,以及一个 span 元素用于显示下拉箭头。select-options 类的 div 则用于存放所有的可选变量,初始状态下是隐藏的。

<div class="container">
    <h1>下拉选择支持多选</h1>
    <div class="select-container">
      <div class="select-input">
        <input type="text" id="selectedVariablesInput" placeholder="请选择变量" readonly>
        <span class="select-arrow"></span>
      </div>
      <div class="select-options">
      </div>
    </div>
</div>

三、CSS 样式设计

为了让组件看起来美观且易于使用,我使用 CSS 进行了样式设计。设置了页面的整体背景颜色、字体等基本样式。对于 container 类,设置了最大宽度、高度、边距、内边距、背景颜色、阴影和圆角,使其在页面中居中显示且具有立体感。 对于 select-input,设置了其显示为弹性布局,设置了边框、圆角、内边距和背景颜色,使其看起来像一个输入框。其中的 input 元素去除了默认边框和轮廓,设置了内边距、字体大小和颜色,使其与外层 div 融合且文字清晰可读。select-arrow 则通过设置三角形的样式来模拟下拉箭头。 select-options 在默认状态下是隐藏的,通过设置 opacity 和 transform 属性实现了显示和隐藏的动画效果。同时,设置了其最大高度、溢出属性、边框、圆角、阴影等,使其在显示时具有良好的视觉效果。每个选项 label 设置了内边距、高度、行高和颜色,并且在鼠标悬停时改变背景颜色以提供交互反馈。

    body {
      font-family: Arial, sans-serif;
      background-color#f5f5f5;
      margin0;
      padding0;
    }

    .container {
      max-width300px;
      height400px;
      margin0 auto;
      padding20px;
      background-color#fff;
      box-shadow0 0 10px rgba(0000.1);
      border-radius5px;
      margin-top50px;
    }

    h1 {
      text-align: center;
      color#333;
      font-size24px;
    }

    .select-container {
      position: relative;
    }

    .select-input {
      display: flex;
      align-items: center;
      border1px solid #e4e7ed;
      border-radius4px;
      padding0 10px;
      cursor: pointer;
      background-color#fff;
    }

    .select-input input {
      flex1;
      border: none;
      outline: none;
      padding10px 0;
      font-size14px;
      cursor: pointer;
      color#555;
    }

    .select-arrow {
      width0;
      height0;
      border-left5px solid transparent;
      border-right5px solid transparent;
      border-top5px solid #c0c4cc;
      margin-left10px;
    }

    .select-options {
      position: absolute;
      topcalc(100% + 10px);
      left0;
      width100%;
      background-color#fff;
      z-index1000;
      max-height200px;
      overflow-y: auto;
      display: none;
      opacity0;
      transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
      transformtranslateY(10px);
      border1px solid #e4e7ed;
      border-radius4px;
      box-shadow0 2px 12px 0 rgba(000, .1);
      box-sizing: border-box;
      margin5px 0;
      padding10px 0;
      scrollbar-width: none;
      -ms-overflow-style: none;
    }

    .select-options::-webkit-scrollbar {
      width0;
      height0;
    }

    .select-options.show {
      display: block;
      opacity1;
      transformtranslateY(0);
    }

    .select-options label {
      display: flex;
      align-items: center;
      padding0 20px;
      cursor: pointer;
      height34px;
      line-height34px;
      color#555;
    }

    .select-options label:hover {
      background-color#f5f7fa;
    }

    .select-options input[type="checkbox"] {
      margin-right10px;
    }

    .selected-variable {
      color#409eff !important;
    }

四、JavaScript 交互逻辑实现

1、生成选项列表: 使用 JavaScript 动态生成了 100 个变量选项。通过 Array.from 方法创建一个包含 100 个元素的数组,每个元素代表一个变量。然后遍历这个数组,为每个变量创建一个 label 元素和一个 input 元素(类型为 checkbox),将它们添加到 select-options 容器中。

const variables = Array.from({ length: 100 }, (_, i) => `变量 ${i + 1}`);
const selectOptions = document.querySelector('.select - options');
variables.forEach(variable => {
    const label = document.createElement('label');
    const input = document.createElement('input');
    input.type = 'checkbox';
    input.value = variable;
    input.classList.add('variable - checkbox');
    label.appendChild(input);
    label.appendChild(document.createTextNode(variable));
    selectOptions.appendChild(label);
});

2、读取和回显选择状态: 从本地缓存 localStorage 中读取之前保存的选择状态。如果存在保存的变量,遍历所有的复选框,将已选中的变量对应的复选框设置为 checked 状态,并为其 label 添加 selected-variable 类,以显示选中的样式。同时,调用 updateSelectedVariablesInput 函数更新输入框的内容。

const savedVariables = JSON.parse(localStorage.getItem('selectedVariables')) || [];
selectOptions.querySelectorAll('.variable - checkbox').forEach(input => {
    if (savedVariables.includes(input.value)) {
        input.checked = true;
        input.parentNode.classList.add('selected - variable');
    }
});
updateSelectedVariablesInput();

3、更新输入框内容: 定义了 updateSelectedVariablesInput 函数,该函数获取所有被选中的变量的值,并将它们以逗号分隔的形式显示在输入框中。如果没有选中任何变量,则显示提示信息。同时,将当前的选择状态保存到本地缓存 localStorage 中。

function updateSelectedVariablesInput() {
    const selectedVariables = Array.from(selectOptions.querySelectorAll('.variable - checkbox:checked'))
     .map(input => input.value);
    selectedVariablesInput.value = selectedVariables.join(', ') || '请选择变量';
    localStorage.setItem('selectedVariables', JSON.stringify(selectedVariables));
}

4、切换选项列表显示状态: 为 select-input 添加点击事件监听器。当点击 select-input 时,如果选项列表当前是显示状态,则隐藏它;如果是隐藏状态,则显示它。

const selectInput = document.querySelector('.select-input');
selectInput.addEventListener('click'function () {
    if (selectOptions.classList.contains('show')) {
        selectOptions.classList.remove('show');
    } else {
        selectOptions.classList.add('show');
    }
});

5、隐藏选项列表: 为整个文档添加点击事件监听器。当点击页面上除了 select - input 和 select - options 之外的其他地方时,隐藏选项列表。

document.addEventListener('click', function (event) {
    if (!selectInput.contains(event.target) &&!selectOptions.contains(event.target)) {
        selectOptions.classList.remove('show');
    }
});

6、监听复选框变化: 为 select - options 添加 change 事件监听器。当复选框的状态发生改变时,根据其是否被选中,为对应的 label 添加或移除 selected - variable 类,并调用 updateSelectedVariablesInput 函数更新输入框内容和本地缓存。

selectOptions.addEventListener('change', function (event) {
    if (event.target.type === 'checkbox') {
        const label = event.target.parentNode;
        if (event.target.checked) {
            label.classList.add('selected-variable');
        } else {
            label.classList.remove('selected-variable');
        }
        updateSelectedVariablesInput();
    }
});

五、总结

起初,面对下拉多选功能的需求,或许大家和我一样,第一反应是依赖现成的 UI 框架。但当我决定尝试用原生 JavaScript 去实现时,才真正打开了前端开发的新世界大门。对于刚入门的前端小白来说,不要害怕尝试原生开发,这能极大地提升我们对前端技术的掌握程度。遇到问题时,多查阅资料,不断调试,每一次解决问题都是一次成长。相信大家在未来的项目中,只要积累足够的经验,都能高效、出色地完成任务,创造出优秀的前端应用。

源代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>下拉选择支持多选</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color#f5f5f5;
      margin0;
      padding0;
    }

    .container {
      max-width300px;
      height400px;
      margin0 auto;
      padding20px;
      background-color#fff;
      box-shadow0 0 10px rgba(0000.1);
      border-radius5px;
      margin-top50px;
    }

    h1 {
      text-align: center;
      color#333;
      font-size24px;
    }

    .select-container {
      position: relative;
    }

    .select-input {
      display: flex;
      align-items: center;
      border1px solid #e4e7ed;
      border-radius4px;
      padding0 10px;
      cursor: pointer;
      background-color#fff;
    }

    .select-input input {
      flex1;
      border: none;
      outline: none;
      padding10px 0;
      font-size14px;
      cursor: pointer;
      color#555;
    }

    .select-arrow {
      width0;
      height0;
      border-left5px solid transparent;
      border-right5px solid transparent;
      border-top5px solid #c0c4cc;
      margin-left10px;
    }

    .select-options {
      position: absolute;
      topcalc(100% + 10px);
      left0;
      width100%;
      background-color#fff;
      z-index1000;
      max-height200px;
      overflow-y: auto;
      display: none;
      opacity0;
      transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
      transformtranslateY(10px);
      border1px solid #e4e7ed;
      border-radius4px;
      box-shadow0 2px 12px 0 rgba(000, .1);
      box-sizing: border-box;
      margin5px 0;
      padding10px 0;
      scrollbar-width: none;
      -ms-overflow-style: none;
    }

    .select-options::-webkit-scrollbar {
      width0;
      height0;
    }

    .select-options.show {
      display: block;
      opacity1;
      transformtranslateY(0);
    }

    .select-options label {
      display: flex;
      align-items: center;
      padding0 20px;
      cursor: pointer;
      height34px;
      line-height34px;
      color#555;
    }

    .select-options label:hover {
      background-color#f5f7fa;
    }

    .select-options input[type="checkbox"] {
      margin-right10px;
    }

    .selected-variable {
      color#409eff !important;
    }
  </style>
</head>

<body>
  <div class="container">
    <h1>下拉选择支持多选</h1>
    <div class="select-container">
      <div class="select-input">
        <input type="text" id="selectedVariablesInput" placeholder="请选择变量" readonly>
        <span class="select-arrow"></span>
      </div>
      <div class="select-options">
      </div>
    </div>
  </div>
  <script>
    document.addEventListener('DOMContentLoaded'function () {
      const variables = Array.from({ length100 }, (_, i) => `变量 ${i + 1}`);
      const selectInput = document.querySelector('.select-input');
      const selectedVariablesInput = document.getElementById('selectedVariablesInput');
      const selectOptions = document.querySelector('.select-options');

      // 动态生成变量选择列表
      variables.forEach(variable => {
        const label = document.createElement('label');
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.value = variable;
        input.classList.add('variable-checkbox');
        label.appendChild(input);
        label.appendChild(document.createTextNode(variable));
        selectOptions.appendChild(label);
      });

      // 从本地缓存中读取之前保存的选择状态并回显到输入框
      const savedVariables = JSON.parse(localStorage.getItem('selectedVariables')) || [];
      selectOptions.querySelectorAll('.variable-checkbox').forEach(input => {
        if (savedVariables.includes(input.value)) {
          input.checked = true;
          input.parentNode.classList.add('selected-variable');
        }
      });
      updateSelectedVariablesInput();

      // 更新输入框内容的函数
      function updateSelectedVariablesInput() {
        const selectedVariables = Array.from(selectOptions.querySelectorAll('.variable-checkbox:checked'))
          .map(input => input.value);
        selectedVariablesInput.value = selectedVariables.join(', ') || '请选择变量';
        // 直接存储选择到本地缓存
        localStorage.setItem('selectedVariables'JSON.stringify(selectedVariables));
      }

      // 点击输入框切换选项列表的显示状态
      selectInput.addEventListener('click'function () {
        if (selectOptions.classList.contains('show')) {
          selectOptions.classList.remove('show');
        } else {
          selectOptions.classList.add('show');
        }
      });

      // 点击页面其他地方隐藏选项列表
      document.addEventListener('click'function (event) {
        if (!selectInput.contains(event.target) && !selectOptions.contains(event.target)) {
          selectOptions.classList.remove('show');
        }
      });

      // 监听复选框的变化并更新输入框内容及本地缓存
      selectOptions.addEventListener('change'function (event) {
        if (event.target.type === 'checkbox') {
          const label = event.target.parentNode;
          if (event.target.checked) {
            label.classList.add('selected-variable');
          } else {
            label.classList.remove('selected-variable');
          }
          updateSelectedVariablesInput();
        }
      });
    });
  </script>
</body>

</html>

SCSS即将废弃`@import`,以前的`@import`该何去何从?

作者 夕水
2025年1月16日 14:30

SCSS即将废弃@import,以前的@import该何去何从?

1. 引言

随着前端开发技术的不断发展,SCSS(Sass)作为一种流行的CSS预处理器,已经在众多前端项目中得到了广泛应用。其优雅的语法、强大的功能以及灵活的模块化机制,使得开发者能够更加高效地构建和管理样式表。然而,随着SCSS的不断迭代更新,我们也迎来了一个重要的变革:@import语法即将被废弃。虽然@import曾经是SCSS的核心功能之一,但它的缺点在实际开发中逐渐暴露出来,特别是在大型项目中,带来了性能和可维护性上的诸多问题。

为了弥补这些缺陷,Sass团队推出了新的@use语法,这一新的语法不仅解决了@import所带来的问题,还提供了更多的功能和更好的模块化支持。本文将深入探讨@import语法的缺点、@use语法的优点,并提供如何从@import过渡到@use的指导,帮助开发者更顺畅地迁移到这一新的语法。

2. SCSS的背景

SCSS是Sass(Syntactically Awesome Stylesheets)的一个语法扩展,主要用于简化CSS的书写方式,并增加一些强大的特性。例如,变量、嵌套、混合宏(mixins)、继承等,极大地提高了CSS的可维护性和复用性。

在SCSS的早期版本中,@import语法被用来引入其他SCSS文件的内容。这种语法让开发者能够在一个文件中引入其他文件,使得样式表可以分成多个部分,提升了项目的可维护性。然而,随着项目规模的扩大,开发者们开始遇到一些性能和管理上的问题,@import语法的缺点逐渐显现。

3. @import语法的缺点

3.1 潜在的命名冲突

@import语法的第一个缺点是命名冲突。当使用@import引入其他文件时,文件中的所有内容(包括变量、混合宏、函数等)会被直接引入到当前文件中。这意味着,如果两个文件中存在相同名称的变量或函数,就可能导致命名冲突。即使这些变量和函数在不同的文件中有不同的用途,一旦它们被合并到一个文件中,就很难保证不会互相覆盖或干扰。

举个例子,如果文件A和文件B都有一个名为$color的变量,而@import会直接将这两个变量引入到全局作用域中,可能导致开发者难以辨别它们究竟来自哪个文件,从而造成意料之外的样式错误。

3.2 全局作用域污染

使用@import时,所有被引入的文件中的内容都会进入全局作用域。这意味着,文件A中定义的任何变量、函数或混合宏,都将与文件B中的内容共享全局作用域。这种“全局污染”问题在小型项目中可能并不明显,但在大型项目中,当文件之间的依赖关系复杂时,可能会导致代码不易追踪和调试。

例如,如果多个开发者同时在不同的文件中修改了同一个全局变量,而他们并不知道其他文件中有类似的修改,就可能导致难以预测的样式错误。在这种情况下,调试的过程不仅费时,而且容易出错。

3.3 性能问题

@import语法的另一个问题是性能。在使用@import时,编译器需要解析并重新编译每一个被引入的文件。即使文件内容没有发生变化,@import仍然会每次都重新加载和编译文件。这在开发过程中可能不会引起显著问题,但在生产环境中,随着项目文件数量的增加,编译时间将会显著增长,甚至可能成为瓶颈,影响开发和构建效率。

例如,在一个包含上百个SCSS文件的大型项目中,@import会导致每个文件都需要被重复解析和编译,这使得构建时间变得非常漫长。这种性能问题尤其在持续集成和自动化部署的过程中表现得更加明显。

3.4 难以控制导入顺序

@import语法在引入文件时,缺乏明确的顺序控制机制。在复杂的SCSS项目中,文件之间的依赖关系往往是错综复杂的,错误的导入顺序可能导致样式不正确或文件加载失败。例如,如果一个文件依赖于另一个文件中的变量或混合宏,而@import语法没有明确的顺序要求,那么变量的值可能会在预期之外发生变化,从而导致样式无法按预期工作。

4. 替代@import的语法:@use

为了克服@import语法的缺点,Sass团队推出了@use语法。@use语法被设计为一种更现代、更高效的引入文件方式,它不仅解决了@import语法的多个问题,还增加了很多新的功能和灵活性。

4.1 模块化与命名空间

@use语法的最大优势之一是它实现了模块化。当你使用@use引入一个SCSS文件时,文件中的变量、函数和混合宏不会直接进入全局作用域,而是会被放置在一个命名空间下。这意味着,你需要通过明确的命名空间来访问这些内容,从而避免了全局污染和命名冲突的问题。

例如,如果你引入一个名为colors.scss的文件,并使用@use 'colors' as c;语法,你就可以通过c.$primary-color来访问该文件中的变量,而不会与其他文件中的变量冲突。

// colors.scss
$primary-color: #2396ef;
$secondary-color: #ff3344;

// main.scss
@use 'colors' as c;

.button {
  background-color: c.$primary-color;
  color: c.$secondary-color;
}

通过使用命名空间,你可以确保每个文件中的变量、函数和混合宏是独立的,不会与其他文件中的内容发生冲突。

4.2 避免重复编译

@use语法的另一个重要特点是它解决了@import带来的性能问题。在使用@use时,被引入的文件会被编译一次,并且在后续的文件中复用已编译的内容。这意味着,即使你多次使用@use引入同一个文件,文件的内容也不会被重复编译,从而提高了编译效率,减少了构建时间。

在大型项目中,@use能够显著减少编译时的冗余工作,提升构建速度。与@import每次重新编译文件不同,@use通过缓存机制有效地避免了不必要的重复工作。

4.3 精确控制导入内容

@use语法允许开发者精确控制引入的内容。你可以通过@use语法引入整个文件,或者只引入文件中的一部分内容。比如,若你只需要引入某个文件中的一个变量,可以使用@useonly关键字来指定只引入特定的部分。

// colors.scss
$primary-color: #2396ef;
$secondary-color: #00ff00;
$tertiary-color: #234455;

// main.scss
@use 'colors' only ($primary-color);

.button {
  background-color: $primary-color;
  color: $secondary-color; // 错误,$secondary-color 未导入
}

这种按需导入的方式,能够使代码更加简洁,避免了不必要的变量和函数被引入到当前作用域中,从而减少了全局污染。

4.4 默认值与配置

@use语法还允许在引入时为文件中的变量设置默认值。例如,如果你引入一个包含主题配置的文件,你可以在@use时覆盖某些默认值,而不需要修改原始文件。这种特性非常有用,尤其是在开发可配置的UI组件时,可以根据需要灵活调整样式。

// _config.scss
$theme-color: #2396ef !default;
$font-size: 16px !default;

// main.scss
@use 'config' with (
  $theme-color: #ff0000,
  $font-size: 18px
);

通过这种方式,你可以轻松地覆盖原文件中的默认设置,而无需修改原文件的源代码,从而提高了灵活性和可扩展性。

4.5 强制使用私有成员

@use语法中的另一个关键特性是,文件中的内容默认是私有的,只有明确导出的变量、函数或混合宏才能被外部访问。这种机制能够增强代码的封装性和安全性,防止不必要的内容被外部访问,从而降低了发生错误的风险。

例如,@use不会直接将文件中的私有变量暴露给外部,这意味着你可以更加清晰地控制哪些内容是公共的,哪些内容是私有的。这种严格的作用域控制机制,提升了代码的可维护性和安全性。

5. 如何过渡到@use

虽然@use带来了诸多优势,但从@import迁移到@use并不是一蹴而就的过程。开发者需要逐步适应新的语法,并进行一些必要的代码重构。以下是一些过渡建议:

5.1 逐步替换

你可以从新文件开始使用@use,而在旧文件中继续使用@import。这样,你可以在项目中逐步替换旧的@import语法,并避免一次性修改所有文件导致的混乱。逐步过渡可以确保迁移过程更加平稳。

5.2 清理全局变量和函数

为了更好地适应@use,你应该清理项目中不必要的全局变量、函数和混合宏。通过将这些内容封装到模块中,避免它们进入全局作用域,你能够更好地利用@use的模块化特性,提升项目的可维护性。

5.3 优化文件结构

使用@use时,你可以更加清晰地组织项目的SCSS文件。将变量、混合宏、函数等内容封装到单独的文件中,并且只暴露必要的部分,从而提高代码的模块化程度。

6. 结语

随着SCSS的逐步更新和优化,@import语法逐渐暴露出了性能和可维护性上的缺陷。@use语法的出现,提供了一个更加现代、灵活和高效的解决方案,不仅解决了@import带来的多个问题,还为开发者提供了更强大的模块化管理能力。随着Sass官方宣布逐步废弃@import,开发者应当尽早适应并过渡到新的@use语法,以提升项目的可维护性、性能和开发效率。

通过逐步替换旧的@import语法、清理全局变量、优化文件结构以及使用@use的新特性,开发者可以在SCSS项目中实现更高效的样式管理,并确保代码的可扩展性和可维护性。虽然过渡可能需要一些时间,但这一变革无疑会在未来的前端开发中带来更加优雅和高效的工作流。

✳️ 本文重点

重点只需要关注新语法的三个用法即可,其它仅供了解,总结如下:

  1. @use "文件名" as 命名空间
  2. @use "文件名" only(具体定义的变量名)
  3. @use "文件名" with(具体变量名: 值 // 覆盖默认值,使用!default关键字定义默认值)

HTML&CSS:2025新年日历卡片

作者 前端Hardy
2025年1月16日 11:55

这段代码创建了一个具有 3D 效果和动态背景的日历卡片,通过 CSS 技术实现了卡片的背景移动、3D 旋转和内容的 3D 位置变化效果,为页面添加了视觉吸引力和用户交互体验。

演示效果

HTML&CSS

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>公众号关注:前端Hardy</title>
    <style>
        body {
            margin0;
            padding0;
            background#212121;
            display: flex;
            align-items: center;
            justify-content: center;
            height100vh;
        }

        .parent {
            height200px;
            width300px;
            padding20px;
            perspective1000px;
        }

        .card {
            padding-top50px;
            border3px solid rgb(255255255);
            transform-style: preserve-3d;
            backgroundlinear-gradient(135deg#0000 18.75%#f3f3f3 0 31.25%#0000 0),
                repeating-linear-gradient(45deg#f3f3f3 -6.25% 6.25%#ffffff 0 18.75%);
            background-size60px 60px;
            background-position0 00 0;
            background-color#f0f0f0;
            width100%;
            box-shadowrgba(1421421420.30px 30px 30px -10px;
            transition: all 0.5s ease-in-out;
        }

        .card:hover {
            background-position: -100px 100px, -100px 100px;
            transformrotate3d(0.50.5010deg);
        }

        .content-box {
            backgroundrgba(41932500.732);
            transition: all 0.5s ease-in-out;
            padding60px 25px 25px 25px;
            transform-style: preserve-3d;
        }

        .content-box .card-title {
            display: inline-block;
            color: white;
            font-size25px;
            font-weight900;
            transition: all 0.5s ease-in-out;
            transformtranslate3d(0px0px50px);
        }

        .content-box .card-title .subtitle {
            display: inline-block;
            color: white;
            font-size16px;
            font-weight900;
            transition: all 0.5s ease-in-out;
            transformtranslate3d(0px0px50px);
        }

        .content-box .card-title:hover {
            transformtranslate3d(0px0px60px);
        }

        .content-box .card-content {
            margin-top10px;
            font-size12px;
            font-weight700;
            color#f2f2f2;
            transition: all 0.5s ease-in-out;
            transformtranslate3d(0px0px30px);
        }

        .content-box .card-content:hover {
            transformtranslate3d(0px0px60px);
        }

        .content-box .see-more {
            cursor: pointer;
            margin-top1rem;
            display: inline-block;
            font-weight900;
            font-size9px;
            text-transform: uppercase;
            colorrgb(7185255);
            background: white;
            padding0.5rem 0.7rem;
            transition: all 0.5s ease-in-out;
            transformtranslate3d(0px0px20px);
        }

        .content-box .see-more:hover {
            transformtranslate3d(0px0px60px);
        }

        .date-box {
            position: absolute;
            top30px;
            right30px;
            height50px;
            width50px;
            background: white;
            border1px solid rgb(7185255);
            padding10px;
            transformtranslate3d(0px0px80px);
            box-shadowrgba(1001001110.20px 17px 10px -10px;
        }

        .date-box span {
            display: block;
            text-align: center;
        }

        .date-box .month {
            colorrgb(4193250);
            font-size11px;
            font-weight700;
            margin-bottom5px;
        }

        .date-box .date {
            font-size20px;
            font-weight900;
            colorrgb(4193250);
        }
    </style>
</head>

<body>
    <div class="parent">
        <div class="card">
            <div class="content-box">
                <span class="card-title">2025 <span class="subtitle">蛇年大吉</span></span>
                <p class="card-content">
                    祥龙降瑞留福韵,金蛇迎春展锦程
                </p>
                <span class="see-more">了解更多</span>
            </div>
            <div class="date-box">
                <span class="month">01</span>
                <span class="date">18</span>
            </div>
        </div>
    </div>
</body>

</html>

HTML 结构

  • parent: 创建一个类名为“parent”的 div 元素,用于包含卡片。
  • card: 创建一个类名为“card”的 div 元素,用于显示卡片内容。
  • content-box: 包含卡片的主要内容。
  • card-title: 显示卡片的标题和副标题。
  • card-content: 显示卡片的内容。
  • see-more: 显示“了解更多”按钮。
  • date-box: 包含日期信息。
  • month: 显示月份。
  • date: 显示日期。

CSS 样式

  • .parent: 设置卡片容器的样式,包括尺寸、内边距和 3D 透视效果。
  • .card: 设置卡片的样式,包括内边距、边框、背景渐变、尺寸和阴影。
  • .card:hover: 设置鼠标悬停在卡片上时的背景位置和 3D 旋转效果。
  • .content-box: 设置卡片内容的样式,包括背景、过渡效果和内边距。
  • .content-box .card-title: 设置卡片标题的样式,包括颜色、字体大小、权重和 3D 位置。
  • .content-box .card-title .subtitle: 设置副标题的样式,包括颜色、字体大小、权重和 3D 位置。
  • .content-box .card-title:hover: 设置鼠标悬停在标题上时的 3D 位置。
  • .content-box .card-content: 设置卡片内容的样式,包括字体大小、权重、颜色和 3D 位置。
  • .content-box .card-content:hover: 设置鼠标悬停在内容上时的 3D 位置。
  • .content-box .see-more: 设置“了解更多”按钮的样式,包括光标、内边距、字体大小、颜色和 3D 位置。
  • .content-box .see-more:hover: 设置鼠标悬停在按钮上时的 3D 位置。
  • .date-box: 设置日期框的样式,包括位置、尺寸、背景、边框、内边距、3D 位置和阴影。
  • .date-box span: 设置日期框内文本的样式,包括显示方式和对齐。
  • .date-box .month: 设置月份的样式,包括颜色、字体大小和权重。
  • .date-box .date: 设置日期的样式,包括字体大小和权重。

HTML&CSS:超丝滑的动态产品卡片

作者 前端Hardy
2025年1月16日 11:53

这段代码创建了一个具有动态效果的产品卡片,通过 CSS 技术实现了卡片的透明度变化、尺寸变化和位置变化效果,为页面添加了视觉吸引力和用户交互体验。

演示效果

HTML&CSS

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>公众号关注:前端Hardy</title>
    <style>
        body {
            margin0;
            padding0;
            background#212121;
            display: flex;
            align-items: center;
            justify-content: center;
            height100vh;
        }

        .card {
            position: relative;
            background: transparent;
            width300px;
            height300px;
            border: none;
        }

        .card:hover {
            width300px;
        }

        .card .container-image {
            position: absolute;
            top50%;
            left50%;
            transformtranslate(-50%, -50%);
            background#e7e7e7;
            width190px;
            height190px;
            cursor: pointer;
            border: none;
            border-radius50%;
            box-shadow0 0 3px 1px #1818183d2px 2px 3px #18181865, inset 2px 2px 2px #ffffff;
            transition: all .3s ease-in-out, opacity .3s;
            transition-delay: .6s0s;
        }

        .card:hover .container-image {
            opacity0;
            border-radius8px;
            transition-delay0s, .6s;
        }

        .card .container-image .image-circle {
            position: absolute;
            top50%;
            left50%;
            transformtranslate(-50%, -50%);
            width125px;
            height: auto;
            object-fit: contain;
            filterdrop-shadow(2px 2px 2px #1818188a);
            transition: all .3s ease-in-out;
            transition-delay: .4s;
        }

        .card:hover .container-image .image-circle {
            opacity0;
            transition-delay0s;
        }

        .card .content {
            display: flex;
            justify-content: space-between;
            align-items: center;
            position: absolute;
            top50%;
            left50%;
            transformtranslate(-50%, -50%);
            background#e7e7e7;
            padding20px;
            width190px;
            height190px;
            cursor: pointer;
            border: none;
            border-radius8px;
            box-shadow0 0 3px 1px #1818183d2px 2px 3px #18181865, inset 2px 2px 2px #ffffff;
            visibility: hidden;
            transition: .3s ease-in-out;
            transition-delay0s;
            z-index1;
        }

        .card:hover .content {
            width290px;
            height190px;
            visibility: visible;
            transition-delay: .5s;
        }

        .card .content .detail {
            display: flex;
            flex-direction: column;
            width100%;
            height100%;
            opacity0;
            transition: all .3s ease-in-out;
            transition-delay0s;
        }

        .card:hover .content .detail {
            color#181818;
            opacity100%;
            transition1s;
            transition-delay: .3s;
        }

        .card .content .detail span {
            margin-bottom5px;
            font-size18px;
            font-weight800;
        }

        .card .content .detail button {
            background#b8854b;
            margin-top: auto;
            width75px;
            height25px;
            color#ffffff;
            font-size13px;
            border: none;
            border-radius8px;
            transition: .3s ease-in-out;
        }

        .card .content .detail button:hover {
            background#d39f63;
        }

        .card .content .product-image {
            position: relative;
            width100%;
            height100%;
        }

        .card .content .product-image .box-image {
            display: flex;
            position: absolute;
            top0;
            left: -25%;
            width100%;
            height115%;
            opacity0;
            transformscale(.5);
            transition: all .5s ease-in-out;
            transition-delay0s;
        }

        .card:hover .content .product-image .box-image {
            top: -25%;
            left0;
            opacity100%;
            transformscale(1);
            transition-delay: .3s;
        }

        .card .content .product-image .box-image .img-product {
            margin: auto;
            width7rem;
            height: auto;
        }

        .fil-shoes1,
        .fil-shoes2 {
            fill: #333333
        }
    </style>
</head>

<body>
    <div class="card">
        <div class="container-image">
            <svg class="image-circle" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1"
                style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
                viewBox="0 0 335.76 195.21" xmlns:xlink="http://www.w3.org/1999/xlink">
                <defs></defs>
                <g id="Layer_x0020_1">
                    <metadata id="CorelCorpID_0Corel-Layer"></metadata>
                    <path class="fil-shoes1"
                        d="M332.99 147.72c-0.87,-8.61 -2.43,-5.69 -1.57,-16.93 0.7,-9.13 -0.29,-27.37 -1.46,-37.14 -0.23,-1.89 -0.43,-5.19 -1.06,-8.26l-3.31 -12.45c-0.54,-1.82 -0.16,-2.7 -0.7,-4.36 -1.5,-4.56 -2.81,-6.58 -3.32,-12.45 -0.27,-3.05 0.85,-4.81 -1.89,-7.13 -1.31,-1.11 -2.14,-1.33 -3.74,-1.23 -10.29,0.69 -19.1,-4.44 -28.23,-7.89l-5.37 -2.51c-7.84,-3.92 -16.02,-10.9 -23.59,-15.81 -5.06,-3.28 -2.36,-0.49 -4.87,-5.83 -2.48,-5.29 -11.1,-6.93 -16.27,-8.5 -2.53,-0.76 -1.72,-0.99 -3.98,-1.68 -1.14,-0.35 -3.14,-0.5 -3.63,-0.76 -2.09,-1.09 -7.48,-4.47 -9.41,-4.76 -3.83,-0.58 -7,6.85 -9.59,10.32 -1.8,2.42 -3.23,5.65 -3.64,8.83 -0.22,1.71 -1.74,3.48 -2.63,5.16 -8.27,-3.97 -8.47,-1.81 -9.27,0.86 -1.69,5.63 -4.59,10.52 -6.25,16.27 -3.05,10.56 -6.49,6.16 -11.04,12.04 -1.64,2.12 -0.97,2.39 -3.42,3.9 -5.38,3.33 -9.5,0.93 -16.05,7.03 -10.09,9.4 -3.03,2.62 -9.55,5.65 -1.43,0.66 -3.15,2.01 -4.26,3.06 -2.1,2.01 -1.92,2.22 -3.22,4.67 -11.67,0 -10.17,6.25 -14.88,7.64 -4.6,1.36 -6.75,1.85 -9.78,5.42 -1.14,1.35 -2.27,3.88 -3.22,4.66 -1.61,1.31 -2.53,0.56 -4.95,2.37 -3.18,2.38 -6.99,3.65 -9.48,5.71 -2.55,2.1 -1.2,1.6 -4.73,3.15 -5.39,2.38 -10.82,3.14 -15.13,7.39 -1.64,1.62 -16.4,4.41 -18.66,4.98 -11.91,3.03 -25.8,4.05 -37.36,8.24 -6.1,2.21 -4.85,-2.22 -11.16,4.05 -4.74,4.71 -3.68,10.8 -6.22,16.29 -1.07,2.31 -1.69,1.85 -2.68,5.2l-1.44 5.87c-0.73,4.22 -2.36,6.72 -1.86,12.16l1.02 4.62c1.95,5.05 7.38,8.45 12.31,10.21l13.44 4.02c5.09,1.37 11.26,1.47 16.51,2.63 5.72,1.26 34.16,1.33 39.85,0.87 2.59,-0.21 3.66,0.35 5.75,0.84 3.42,0.8 4.45,-0.44 7.03,-0.28 2.33,0.14 3.31,1.06 6.8,1.09 9.62,0.08 90.6,0.66 98.33,-0.28 4.23,-0.52 10.35,0.74 14.86,0.26 11.36,-1.21 24.28,-2.91 36.17,-1.87 7.05,0.61 29.63,1.01 33.07,-1.51 1.48,0.99 29.81,-0.46 33.72,-0.68 8.78,-0.5 17.29,-6.69 16.8,-15.89 -0.1,-9.37 -1.8,-17.8 -2.75,-27.26z">
                    </path>
                </g>
            </svg>
        </div>
        <div class="content">
            <div class="detail">
                <span>李宁 <br> 春节新款.</span>
                <p>¥299</p>
                <button>立即抢购</button>
            </div>
            <div class="product-image">
                <div class="box-image">
                    <svg class="img-product" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1"
                        style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
                        viewBox="0 0 116.83 182.61" xmlns:xlink="http://www.w3.org/1999/xlink">
                        <defs></defs>
                        <g id="Layer_x0020_1">
                            <metadata id="CorelCorpID_0Corel-Layer"></metadata>
                            <path class="fil-shoes2"
                                d="M99.33 20.55c-4.24,-1.91 -3.3,-0.4 -8.3,-3.82 -4.06,-2.78 -12.82,-7.22 -17.68,-9.3 -0.94,-0.4 -2.53,-1.19 -4.13,-1.73l-6.69 -1.8c-0.99,-0.23 -1.3,-0.65 -2.22,-0.84 -2.53,-0.53 -3.82,-0.46 -6.69,-1.8 -1.49,-0.7 -2.01,-1.69 -3.83,-1.04 -0.87,0.31 -1.19,0.64 -1.58,1.41 -2.44,4.98 -7.2,7.7 -11.26,11.02l-2.61 1.83c-3.93,2.6 -9.38,4.53 -13.7,6.73 -2.89,1.47 -0.86,0.97 -4.02,0.7 -3.13,-0.27 -6.21,3.31 -8.33,5.29 -1.04,0.97 -0.92,0.53 -1.85,1.4 -0.47,0.44 -1.08,1.33 -1.33,1.49 -1.07,0.68 -4.09,2.28 -4.75,3.1 -1.3,1.63 1.31,5.1 2.23,7.24 0.64,1.49 1.76,3.02 3.13,4.07 0.74,0.56 1.15,1.74 1.7,2.61 -4.07,2.78 -3.12,3.46 -2.09,4.55 2.17,2.3 3.66,4.97 5.89,7.28 4.1,4.26 1.12,4.68 2.64,8.38 0.55,1.33 0.85,1.09 0.9,2.64 0.11,3.4 -2.12,4.67 -1.04,9.36 1.66,7.23 0.4,2.11 0.06,5.96 -0.07,0.84 0.09,2.01 0.28,2.8 0.37,1.51 0.52,1.49 1.31,2.75 -3.14,5.43 0.18,6.41 -0.44,8.98 -0.6,2.51 -0.95,3.64 -0.11,6.01 0.32,0.9 1.2,2.1 1.3,2.75 0.18,1.1 -0.42,1.33 -0.22,2.94 0.25,2.12 -0.18,4.23 0.11,5.95 0.29,1.75 0.42,0.99 0.2,3.05 -0.34,3.15 -1.45,5.88 -0.63,9.03 0.31,1.2 -2.36,8.82 -2.7,10.03 -1.79,6.36 -5.05,13.1 -6.2,19.61 -0.61,3.43 -2.34,1.66 -1.11,6.28 0.92,3.47 4.04,4.61 5.91,7.28 0.79,1.12 0.41,1.28 1.7,2.65l2.35 2.25c1.77,1.48 2.49,2.9 5.16,4.13l2.42 0.76c2.87,0.45 5.92,-1.16 8.06,-2.98l5.48 -5.18c2.01,-2 3.71,-4.85 5.66,-6.98 2.12,-2.32 9.8,-15.54 11.11,-18.32 0.6,-1.26 1.15,-1.61 1.93,-2.45 1.29,-1.38 0.99,-2.19 1.76,-3.35 0.69,-1.05 1.38,-1.26 2.33,-2.87 2.62,-4.45 24.66,-42 26.3,-45.85 0.9,-2.11 3.13,-4.62 4.12,-6.85 2.49,-5.61 5.17,-12.08 8.85,-17.34 2.18,-3.12 8.43,-13.52 8.19,-15.8 0.86,-0.42 7.8,-14 8.75,-15.88 2.13,-4.22 1.53,-9.85 -2.88,-12.09 -4.39,-2.47 -8.77,-3.95 -13.43,-6.05z">
                            </path>
                        </g>
                    </svg>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

HTML 结构

  • card: 创建一个类名为“card”的 div 元素,用于包含整个产品卡片。
  • container-image: 包含圆形图片的 div。
  • image-circle: 创建一个 SVG 圆形图片。
  • content: 包含产品详细信息的 div。
  • detail: 包含产品名称、价格和购买按钮的 div。
  • product-image: 包含产品图片的 div。
  • box-image: 包含 SVG 产品图片的 div。

CSS 样式

  • .card: 设置卡片的样式,包括位置、背景、尺寸和边框。
  • .card:hover: 设置鼠标悬停在卡片上时的样式。
  • .container-image: 设置圆形图片容器的样式,包括位置、背景、尺寸、边框半径和阴影。
  • .card:hover .container-image: 设置鼠标悬停在卡片上时圆形图片容器的样式,使其透明度变为 0。
  • .image-circle: 设置圆形图片的样式,包括位置、尺寸和滤镜效果。
  • .card:hover .container-image .image-circle: 设置鼠标悬停在卡片上时圆形图片的样式,使其透明度变为 0。
  • .content: 设置产品详细信息容器的样式,包括位置、背景、尺寸、边框半径和可见性。
  • .card:hover .content: 设置鼠标悬停在卡片上时产品详细信息容器的样式,使其可见并调整尺寸。
  • .detail: 设置产品详细信息的样式,包括显示方式、尺寸和透明度。
  • .card:hover .content .detail: 设置鼠标悬停在卡片上时产品详细信息的样式,使其可见。
  • .detail span: 设置产品名称的样式,包括字体大小和权重。
  • .detail button: 设置购买按钮的样式,包括背景、尺寸、颜色和边框半径。
  • .detail button:hover: 设置鼠标悬停在购买按钮上时的样式。
  • .product-image: 设置产品图片容器的样式,包括位置和尺寸。
  • .box-image: 设置产品图片的样式,包括位置、尺寸和透明度。
  • .card:hover .content .product-image .box-image: 设置鼠标悬停在卡片上时产品图片的样式,使其可见并调整位置和尺寸。
  • .img-product: 设置产品图片的样式,包括尺寸。

HTML&CSS:超级使用的点赞特效卡片!建议收藏

作者 前端Hardy
2025年1月16日 11:53

这段代码是一个 HTML 页面,它包含了 CSS 样式,用于创建一个带有“点赞”和“不喜欢”功能的交互式卡片。卡片上有一个关闭按钮,用户可以点击“点赞”或“不喜欢”按钮,并且有动画效果。

演示效果

HTML&CSS

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>公众号关注:前端Hardy</title>
    <style>
        body {
            margin0;
            padding0;
            background#e8e8e8;
            display: flex;
            align-items: center;
            justify-content: center;
            height100vh;
        }

        .like-dislike-container {
            --dark-grey#353535;
            --middle-grey#767676;
            --lightest-greylinear-gradient(#fafafa#ebebeb);
            --shadow0 5px 15px 0 #00000026;
            --shadow-active0 5px 5px 0 #00000026;
            --border-radius-main10px;
            --border-radius-icon50px;
            position: relative;
            display: flex;
            text-align: center;
            flex-direction: column;
            align-items: center;
            cursor: default;
            colorvar(--dark-grey);
            opacity: .9;
            margin: auto;
            padding1.5rem;
            font-weight600;
            backgroundvar(--lightest-grey);
            max-width: max-content;
            border-radiusvar(--border-radius-main);
            box-shadowvar(--shadow);
            transition: .2s ease all;
        }

        .like-dislike-container:hover {
            box-shadowvar(--shadow-active);
        }

        .like-dislike-container .tool-box {
            position: absolute;
            display: flex;
            align-items: center;
            justify-content: center;
            width2.5rem;
            height2.5rem;
            top0;
            right0;
            border-radiusvar(--border-radius-main);
        }

        .like-dislike-container .btn-close {
            display: flex;
            align-items: center;
            justify-content: center;
            text-align: center;
            width: .8rem;
            height: .8rem;
            color: transparent;
            font-size0;
            cursor: pointer;
            background-color#ff000080;
            border: none;
            border-radiusvar(--border-radius-main);
            transition: .2s ease all;
        }

        .like-dislike-container .btn-close:hover {
            width1rem;
            height1rem;
            font-size1rem;
            color#ffffff;
            background-color#ff0000cc;
            box-shadowvar(--shadow-active);
        }

        .like-dislike-container .btn-close:active {
            width: .9rem;
            height: .9rem;
            font-size: .9rem;
            color#ffffffde;
            --shadow-btn-close0 3px 3px 0 #00000026;
            box-shadowvar(--shadow-btn-close);
        }

        .like-dislike-container .text-content {
            margin-bottom1rem;
            font-size15px;
            line-height1.6;
            cursor: default;
        }

        .like-dislike-container .icons-box {
            display: flex;
        }

        .like-dislike-container .icons {
            position: relative;
            display: flex;
            justify-content: center;
            align-items: center;
            opacity: .6;
            margin0 0.5rem;
            cursor: pointer;
            user-select: none;
            border1px solid var(--middle-grey);
            border-radiusvar(--border-radius-icon);
            transition: .2s ease all;
        }

        .like-dislike-container .icons:hover {
            opacity: .9;
            box-shadowvar(--shadow);
        }

        .like-dislike-container .icons:active {
            opacity: .9;
            box-shadowvar(--shadow-active);
        }

        .like-dislike-container .icons .btn-label {
            display: flex;
            justify-content: center;
            align-items: center;
            padding0 0.5rem;
            cursor: pointer;
            position: relative;
        }

        .like-dislike-container .like-text-content {
            border-right0.1rem solid var(--dark-grey);
            padding0 0.6rem 0 0.5rem;
            pointer-events: none;
        }

        .like-dislike-container .dislike-text-content {
            border-left0.1rem solid var(--dark-grey);
            padding0 0.5rem 0 0.6rem;
            pointer-events: none;
        }

        .like-dislike-container .icons .svgs {
            width1.3rem;
            fill: #000000;
            box-sizing: content-box;
            padding10px 10px;
            transition: .2s ease all;
        }

        .like-dislike-container .icons .input-box {
            position: absolute;
            opacity0;
            cursor: pointer;
            height0;
            width0;
        }

        .like-dislike-container .icons #icon-like-regular {
            display: block;
        }

        .like-dislike-container .icons #icon-like-solid {
            display: none;
        }

        .like-dislike-container .icons:hover :is(#icon-like-solid#icon-like-regular) {
            animation: rotate-icon-like 0.7s ease-in-out both;
        }

        .like-dislike-container .icons #like-checkbox:checked~#icon-like-regular {
            display: none;
            animation: checked-icon-like 0.5s;
        }

        .like-dislike-container .icons #like-checkbox:checked~#icon-like-solid {
            display: block;
            animation: checked-icon-like 0.5s;
        }

        .like-dislike-container .icons #icon-dislike-regular {
            display: block;
            transformrotate(180deg);
        }

        .like-dislike-container .icons #icon-dislike-solid {
            display: none;
            transformrotate(180deg);
        }

        .like-dislike-container .icons:hover :is(#icon-dislike-solid#icon-dislike-regular) {
            animation: rotate-icon-dislike 0.7s ease-in-out both;
        }

        .like-dislike-container .icons #dislike-checkbox:checked~#icon-dislike-regular {
            display: none;
            animation: checked-icon-dislike 0.5s;
        }

        .like-dislike-container .icons #dislike-checkbox:checked~#icon-dislike-solid {
            display: block;
            animation: checked-icon-dislike 0.5s;
        }

        .like-dislike-container .icons .fireworks {
            transformscale(0.4);
        }

        .like-dislike-container .icons #like-checkbox:checked~.fireworks>.checked-like-fx {
            position: absolute;
            width10px;
            height10px;
            right40px;
            border-radius50%;
            box-shadow0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff;
            animation1s fireworks-bang ease-out forwards, 1s fireworks-gravity ease-in forwards, 5s fireworks-position linear forwards;
            animation-duration1.25s1.25s6.25s;
        }

        .like-dislike-container .icons #dislike-checkbox:checked~.fireworks>.checked-dislike-fx {
            position: absolute;
            width10px;
            height10px;
            left40px;
            border-radius50%;
            box-shadow0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff0 0 #fff;
            animation1s fireworks-bang ease-out forwards, 1s fireworks-gravity ease-in forwards, 5s fireworks-position linear forwards;
            animation-duration1.25s1.25s6.25s;
        }

        @keyframes rotate-icon-like {
            0% {
                transformrotate(0degtranslate3d(000);
            }

            25% {
                transformrotate(3degtranslate3d(000);
            }

            50% {
                transformrotate(-3degtranslate3d(000);
            }

            75% {
                transformrotate(1degtranslate3d(000);
            }

            100% {
                transformrotate(0degtranslate3d(000);
            }
        }

        @keyframes rotate-icon-dislike {
            0% {
                transformrotate(180degtranslate3d(000);
            }

            25% {
                transformrotate(183degtranslate3d(000);
            }

            50% {
                transformrotate(177degtranslate3d(000);
            }

            75% {
                transformrotate(181degtranslate3d(000);
            }

            100% {
                transformrotate(180degtranslate3d(000);
            }
        }

        @keyframes checked-icon-like {
            0% {
                transformscale(0);
                opacity0;
            }

            50% {
                transformscale(1.2rotate(-10deg);
            }
        }

        @keyframes checked-icon-dislike {
            0% {
                transformscale(0rotate(180deg);
                opacity0;
            }

            50% {
                transformscale(1.2rotate(170deg);
            }
        }

        @keyframes fireworks-position {

            0%,
            19.9% {
                margin-top10%;
                margin-left40%;
            }

            20%,
            39.9% {
                margin-top40%;
                margin-left30%;
            }

            40%,
            59.9% {
                margin-top20%;
                margin-left70%;
            }

            60%,
            79.9% {
                margin-top30%;
                margin-left20%;
            }

            80%,
            99.9% {
                margin-top30%;
                margin-left80%;
            }
        }

        @keyframes fireworks-gravity {
            to {
                transformtranslateY(200px);
                opacity0;
            }
        }

        @keyframes fireworks-bang {
            to {
                box-shadow114px -107.3333333333px #8800ff212px -166.3333333333px #a600ff197px -6.3333333333px #ff006a179px -329.3333333333px #3300ff, -167px -262.3333333333px #ff0062233px 65.6666666667px #ff008c81px 42.6666666667px #0051ff, -13px 54.6666666667px #00ff2b, -60px -183.3333333333px #0900ff127px -259.3333333333px #ff00e6117px -122.3333333333px #00b7ff95px 20.6666666667px #ff8000115px 1.6666666667px #0004ff, -160px -328.3333333333px #00ff4069px -242.3333333333px #000dff, -208px -230.3333333333px #ff040030px -15.3333333333px #e6ff00235px -15.3333333333px #fb00ff80px -232.3333333333px #d5ff00175px -173.3333333333px #00ff3c, -187px -176.3333333333px #aaff004px 26.6666666667px #ff6f00227px -106.3333333333px #ff0099119px 17.6666666667px #00ffd5, -102px 4.6666666667px #ff0088, -16px -4.3333333333px #00fff7, -201px -310.3333333333px #00ffdd64px -181.3333333333px #f700ff, -234px -15.3333333333px #00fffb, -184px -263.3333333333px #aa00ff96px -303.3333333333px #0037ff, -139px 10.6666666667px #0026ff25px -205.3333333333px #00ff2b, -129px -322.3333333333px #40ff00, -235px -187.3333333333px #26ff00, -136px -237.3333333333px #0091ff, -82px -321.3333333333px #6a00ff7px -267.3333333333px #ff00c8, -155px 30.6666666667px #0059ff, -85px -73.3333333333px #6a00ff60px -199.3333333333px #55ff00, -9px -289.3333333333px #00ffaa, -208px -167.3333333333px #00ff80, -13px -299.3333333333px #ff0004179px -164.3333333333px #ff0044, -112px 12.6666666667px #0051ff, -209px -125.3333333333px #ff00bb14px -101.3333333333px #00ff95, -184px -292.3333333333px #ff0099, -26px -168.3333333333px #09ff00129px -67.3333333333px #0084ff, -17px -23.3333333333px #0059ff129px 34.6666666667px #7300ff35px -24.3333333333px #ffd900, -12px -297.3333333333px #ff8400129px -156.3333333333px #0dff00157px -29.3333333333px #1a00ff, -221px 6.6666666667px #ff00620px -311.3333333333px #ff006a155px 50.6666666667px #00ffaa, -71px -318.3333333333px #0073ff;
            }
        }
    </style>
</head>

<body>
    <div class="like-dislike-container">
        <div class="tool-box">
            <button class="btn-close">×</button>
        </div>
        <p class="text-content">您觉得这篇文章<br>怎么样?</p>
        <div class="icons-box">
            <div class="icons">
                <label class="btn-label" for="like-checkbox">
                    <span class="like-text-content">123</span>
                    <input class="input-box" id="like-checkbox" type="checkbox">
                    <svg class="svgs" id="icon-like-solid" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                        <path
                            d="M313.4 32.9c26 5.2 42.9 30.5 37.7 56.5l-2.3 11.4c-5.3 26.7-15.1 52.1-28.8 75.2H464c26.5 0 48 21.5 48 48c0 18.5-10.5 34.6-25.9 42.6C497 275.4 504 288.9 504 304c0 23.4-16.8 42.9-38.9 47.1c4.4 7.3 6.9 15.8 6.9 24.9c0 21.3-13.9 39.4-33.1 45.6c.7 3.3 1.1 6.8 1.1 10.4c0 26.5-21.5 48-48 48H294.5c-19 0-37.5-5.6-53.3-16.1l-38.5-25.7C176 420.4 160 390.4 160 358.3V320 272 247.1c0-29.2 13.3-56.7 36-75l7.4-5.9c26.5-21.2 44.6-51 51.2-84.2l2.3-11.4c5.2-26 30.5-42.9 56.5-37.7zM32 192H96c17.7 0 32 14.3 32 32V448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32V224c0-17.7 14.3-32 32-32z">
                        </path>
                    </svg>
                    <svg class="svgs" id="icon-like-regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                        <path
                            d="M323.8 34.8c-38.2-10.9-78.1 11.2-89 49.4l-5.7 20c-3.7 13-10.4 25-19.5 35l-51.3 56.4c-8.9 9.8-8.2 25 1.6 33.9s25 8.2 33.9-1.6l51.3-56.4c14.1-15.5 24.4-34 30.1-54.1l5.7-20c3.6-12.7 16.9-20.1 29.7-16.5s20.1 16.9 16.5 29.7l-5.7 20c-5.7 19.9-14.7 38.7-26.6 55.5c-5.2 7.3-5.8 16.9-1.7 24.9s12.3 13 21.3 13L448 224c8.8 0 16 7.2 16 16c0 6.8-4.3 12.7-10.4 15c-7.4 2.8-13 9-14.9 16.7s.1 15.8 5.3 21.7c2.5 2.8 4 6.5 4 10.6c0 7.8-5.6 14.3-13 15.7c-8.2 1.6-15.1 7.3-18 15.1s-1.6 16.7 3.6 23.3c2.1 2.7 3.4 6.1 3.4 9.9c0 6.7-4.2 12.6-10.2 14.9c-11.5 4.5-17.7 16.9-14.4 28.8c.4 1.3 .6 2.8 .6 4.3c0 8.8-7.2 16-16 16H286.5c-12.6 0-25-3.7-35.5-10.7l-61.7-41.1c-11-7.4-25.9-4.4-33.3 6.7s-4.4 25.9 6.7 33.3l61.7 41.1c18.4 12.3 40 18.8 62.1 18.8H384c34.7 0 62.9-27.6 64-62c14.6-11.7 24-29.7 24-50c0-4.5-.5-8.8-1.3-13c15.4-11.7 25.3-30.2 25.3-51c0-6.5-1-12.8-2.8-18.7C504.8 273.7 512 257.7 512 240c0-35.3-28.6-64-64-64l-92.3 0c4.7-10.4 8.7-21.2 11.8-32.2l5.7-20c10.9-38.2-11.2-78.1-49.4-89zM32 192c-17.7 0-32 14.3-32 32V448c0 17.7 14.3 32 32 32H96c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32H32z">
                        </path>
                    </svg>
                    <div class="fireworks">
                        <div class="checked-like-fx"></div>
                    </div>
                </label>
            </div>
            <div class="icons">
                <label class="btn-label" for="dislike-checkbox">
                    <input class="input-box" id="dislike-checkbox" type="checkbox">
                    <div class="fireworks">
                        <div class="checked-dislike-fx"></div>
                    </div>
                    <svg class="svgs" id="icon-dislike-solid" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                        <path
                            d="M313.4 32.9c26 5.2 42.9 30.5 37.7 56.5l-2.3 11.4c-5.3 26.7-15.1 52.1-28.8 75.2H464c26.5 0 48 21.5 48 48c0 18.5-10.5 34.6-25.9 42.6C497 275.4 504 288.9 504 304c0 23.4-16.8 42.9-38.9 47.1c4.4 7.3 6.9 15.8 6.9 24.9c0 21.3-13.9 39.4-33.1 45.6c.7 3.3 1.1 6.8 1.1 10.4c0 26.5-21.5 48-48 48H294.5c-19 0-37.5-5.6-53.3-16.1l-38.5-25.7C176 420.4 160 390.4 160 358.3V320 272 247.1c0-29.2 13.3-56.7 36-75l7.4-5.9c26.5-21.2 44.6-51 51.2-84.2l2.3-11.4c5.2-26 30.5-42.9 56.5-37.7zM32 192H96c17.7 0 32 14.3 32 32V448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32V224c0-17.7 14.3-32 32-32z">
                        </path>
                    </svg>
                    <svg class="svgs" id="icon-dislike-regular" xmlns="http://www.w3.org/2000/svg"
                        viewBox="0 0 512 512">
                        <path
                            d="M323.8 34.8c-38.2-10.9-78.1 11.2-89 49.4l-5.7 20c-3.7 13-10.4 25-19.5 35l-51.3 56.4c-8.9 9.8-8.2 25 1.6 33.9s25 8.2 33.9-1.6l51.3-56.4c14.1-15.5 24.4-34 30.1-54.1l5.7-20c3.6-12.7 16.9-20.1 29.7-16.5s20.1 16.9 16.5 29.7l-5.7 20c-5.7 19.9-14.7 38.7-26.6 55.5c-5.2 7.3-5.8 16.9-1.7 24.9s12.3 13 21.3 13L448 224c8.8 0 16 7.2 16 16c0 6.8-4.3 12.7-10.4 15c-7.4 2.8-13 9-14.9 16.7s.1 15.8 5.3 21.7c2.5 2.8 4 6.5 4 10.6c0 7.8-5.6 14.3-13 15.7c-8.2 1.6-15.1 7.3-18 15.1s-1.6 16.7 3.6 23.3c2.1 2.7 3.4 6.1 3.4 9.9c0 6.7-4.2 12.6-10.2 14.9c-11.5 4.5-17.7 16.9-14.4 28.8c.4 1.3 .6 2.8 .6 4.3c0 8.8-7.2 16-16 16H286.5c-12.6 0-25-3.7-35.5-10.7l-61.7-41.1c-11-7.4-25.9-4.4-33.3 6.7s-4.4 25.9 6.7 33.3l61.7 41.1c18.4 12.3 40 18.8 62.1 18.8H384c34.7 0 62.9-27.6 64-62c14.6-11.7 24-29.7 24-50c0-4.5-.5-8.8-1.3-13c15.4-11.7 25.3-30.2 25.3-51c0-6.5-1-12.8-2.8-18.7C504.8 273.7 512 257.7 512 240c0-35.3-28.6-64-64-64l-92.3 0c4.7-10.4 8.7-21.2 11.8-32.2l5.7-20c10.9-38.2-11.2-78.1-49.4-89zM32 192c-17.7 0-32 14.3-32 32V448c0 17.7 14.3 32 32 32H96c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32H32z">
                        </path>
                    </svg>
                    <span class="dislike-text-content">4</span>
                </label>
            </div>
        </div>
    </div>
</body>

</html>

HTML 结构

  • like-dislike-container: 创建一个类名为“like-dislike-container”的 div 元素,用于包含整个卡片。
  • tool-box: 包含工具栏的 div。
  • btn-close: 创建一个关闭按钮。
  • text-content: 显示文本“您觉得这篇文章怎么样?”。
  • icons-box: 包含“点赞”和“不喜欢”按钮的 div。
  • icons: 元素,每个元素包含一个按钮。
  • btn-label: 创建一个标签,用于“点赞”按钮。
  • input-box like-checkbox : 创建一个复选框,用于“点赞”状态。
  • svgs: 创建两个 SVG 图标,一个用于“点赞”状态,一个用于默认状态。
  • fireworks: 创建一个用于显示烟花效果的 div。
  • btn-label: 创建一个标签,用于“不喜欢”按钮。
  • input-box: 创建一个复选框,用于“不喜欢”状态。
  • svgs icon-dislike-solid: 创建两个 SVG 图标,一个用于“不喜欢”状态,一个用于默认状态。

CSS 样式

  • .like-dislike-container: 设置卡片的样式,包括尺寸、背景渐变、边框半径、阴影和鼠标指针样式。
  • .like-dislike-container:hover: 设置鼠标悬停在卡片上时的阴影效果。
  • .tool-box: 设置工具栏的样式,包括位置、尺寸和对齐。
  • .btn-close: 设置关闭按钮的样式,包括尺寸、颜色、字体大小和鼠标指针样式。
  • .btn-close:hover 和 .btn-close:active: 设置关闭按钮在鼠标悬停和点击时的样式。
  • .text-content: 设置文本内容的样式,包括字体大小和行高。
  • .icons-box: 设置图标盒子的样式,包括显示方式。
  • .icons: 设置图标的样式,包括位置、尺寸、透明度和鼠标指针样式。
  • .icons:hover 和 .icons:active: 设置图标在鼠标悬停和点击时的样式。
  • .btn-label: 设置按钮标签的样式,包括位置和尺寸。
  • .like-text-content 和 .dislike-text-content: 设置“点赞”和“不喜欢”文本的样式,包括边框和内边距。
  • .svgs: 设置 SVG 图标的样式,包括尺寸和填充。
  • .input-box: 设置复选框的样式,包括位置和透明度。
  • .fireworks: 设置烟花效果的样式,包括位置和尺寸。
  • @keyframes rotate-icon-like 和 @keyframes rotate-icon-dislike: 定义图标旋转的动画效果。
  • @keyframes checked-icon-like 和 @keyframes checked-icon-dislike: 定义图标选中状态的动画效果。
  • @keyframes fireworks-position, @keyframes fireworks-gravity, @keyframes fireworks-bang: 定义烟花效果的动画效果。

CSS 重置样式表:让你的页面从零开始

2025年1月16日 02:28

前言

在网页开发过程中,浏览器会自动应用一些默认的样式,这些样式虽然有助于快速显示内容,但它们在不同浏览器间可能存在差异,从而影响页面的布局和一致性。为了确保项目在各浏览器中有统一的展示效果,我们通常会使用 CSS 重置样式表(CSS Reset),清理这些默认样式,从而为我们的设计提供一个干净、统一的起点。

本文将介绍一份常见的 CSS 重置样式表,帮助你清理不必要的默认样式,并为你的项目构建一个一致的基础。

代码实现

/* 使用更直观的盒模型(box-sizing) */
*, *::before, *::after {
  font-family: inherit;
  box-sizing: border-box;
}

/* 取消默认的内外边距 margin 和 padding */
body, h1, h2, h3, h4, h5, h6, dl, dt, dd, ol, ul , li, p, fieldset, legend, header, footer, hgroup, menu, nav, section, figcaption, figure, mark, form,
time, td, th, label, hr, i, strong {
  margin: 0;
  padding: 0;
  font-style: normal;
  font-weight: normal;
  font-size: inherit;
}

/* 新布局元素兼容旧版本浏览器 */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
  display: block;
}

/* 允许通过百分比设置应用的高度 */
html, body, #app {
  width: 100%;
  height: 100%;
  line-height: 1;
  -webkit-font-smoothing: antialiased; /* 优化文字渲染平滑度 */
}

/* 解决在移动端时,点击元素会出现阴影的问题 */
body {
  -webkit-tap-highlight-color: transparent;
}

/* 去除所有项目符号标识 */
li {
  list-style: none;
}

/* 去除超链接默认下划线 */
a {
  color: inherit;
  text-decoration: none;
}

/* 给超链接添加一个默认的 hover 颜色 */
a:hover {
  cursor: pointer;
}

/* 去除图片与容器底部的留白,使多媒体内容显示更合理 */
img, iframe, video, canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* 去除 input 标签的默认样式 */
button, input, textarea {
  margin: 0;
  padding: 0;
  border: 1px solid #dcdfe6;
  outline: none;
  font-size: inherit;
  background-color: transparent;
}

/* input placeholder 文字的样式 */
::-webkit-input-placeholder {
  color: #afbdcc;
}

/* 禁止文本域手动拖动大小 */
textarea {
  resize: none;
}

/* 解决 Chrome 浏览器默认黄色背景问题 */
input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill {
  -webkit-box-shadow: 0 0 0 1000px #fff inset;
}

/* 自定义滚动条样式 */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

/* 滚动条轨道样式 */
::-webkit-scrollbar-track {
  background-color: #f5f5f5;
}

/* 滚动条滑块样式 */
::-webkit-scrollbar-thumb {
  border-radius: 6px;
  background-color: #ccc;
}
::-webkit-scrollbar-thumb:hover {
  background-color: #a8a8a8;
}
::-webkit-scrollbar-thumb:active {
  background-color: #787878;
}

/* 当垂直与水平滚动条同时存在时的交汇部分样式 */
::-webkit-scrollbar-corner {
  background-color: #f5f5f5;
}

/* 其他元素的 corner 部分样式(例如:textarea 可拖动按钮) */
::-webkit-resizer {
  background-repeat: no-repeat; 
  background-position: bottom right;
}

内容解析

1、盒模型(box-sizing)优化

默认情况下,元素的 widthheight 是基于 content-box 算的,这意味着 padding 和 border 会被加到元素的实际宽高上。通过使用 box-sizing: border-box,我们确保元素的宽高包括 padding 和 border,这样就能避免一些布局上的问题,使得开发时更直观,减少了因盒模型导致的布局错误。

2、清理默认的 margin 和 padding

不同浏览器会为许多元素自动添加 marginpadding,这可能导致页面布局不一致。通过重置这些默认值,我们为页面提供一个干净的起点,避免了不必要的空隙或布局冲突。

3、元素兼容性

HTML5 引入了一些新元素,如 article, section 等,这些元素在旧版浏览器中默认并没有设置为 block,导致它们不正常显示。通过显式地为这些元素设置 display: block,我们可以确保在所有浏览器中都能正确渲染。

4、字体优化和渲染平滑度

为了改善字体在不同设备和浏览器上的渲染效果,使用了 -webkit-font-smoothing: antialiased。这项优化可以提升字体的显示质量,尤其在高分辨率的屏幕上,显得更为明显。

5、移动端优化

在移动设备上,点击元素时浏览器会默认显示一个点击阴影效果。通过 -webkit-tap-highlight-color: transparent;,我们去除了这个阴影,使得用户在点击时的体验更加顺畅、清晰。

6、图片、视频和 iframe 展示优化

为了避免图片、视频、iframe 等元素在容器内展示时出现不必要的空白区域,我们设置了 display: blockwidth: 100%,确保这些多媒体内容能够充分填满其父元素,不会影响整体布局。

7、表单控件的样式重置

button, input, textarea 等表单控件在浏览器中的默认样式通常不一致,通过重置它们的边框、背景、内外边距等,我们能够确保表单控件的一致性,并且更容易应用自定义样式。

8、自定义滚动条样式

在 Webkit 内核的浏览器中,我们通过 ::-webkit-scrollbar 等伪元素来自定义滚动条的外观,保证滚动条与页面整体风格一致,提升用户体验。

总结

通过这份 CSS 重置样式表,你可以有效消除浏览器默认样式带来的不一致问题,为你的项目提供一个统一的视觉起点。这不仅能提升页面在不同浏览器中的一致性,还能节省开发过程中调试布局和兼容性问题的时间。

如果你希望更加专注于页面的功能与设计,而不必为各浏览器间的样式差异烦恼,这份 CSS 重置样式表将是一个非常有效的工具。如果你有更多关于 CSS 重置或其他前端问题的讨论,欢迎在评论区留言,我们一起探讨!

❌
❌