从Vant图标的CSS文件提取图标文件

server/2025/1/15 8:25:57/

文章目录

  • 环境
  • 背景
    • 基础用法
    • 使用图片URL:
    • 自定义图标
  • 问题
  • 分析
  • 步骤
    • 步骤1:解码
    • 步骤2:提取图标
    • 步骤3:批量提取图标
    • 完整代码和用法
  • 总结
  • 参考

环境

  • Windows 11
  • Python 3.13.1
  • Vant 4.9.15
  • NPM 11.0.0

背景

我需要一些图标文件,在网上搜来搜去,后来找到了Vant组件( https://vant-ui.github.io/vant/#/zh-CN/ )。

Vant组件的图标: https://vant-ui.github.io/vant/#/zh-CN/icon

注:关于如何安装和使用Vant,可以参考我另一篇文档( https://blog.csdn.net/duke_ding2/article/details/136131991 )。写的时间有点久了,不过还是能work的。

Vant图标大致有3种用法:

  • 基础用法
  • 使用图片URL
  • 自定义图标

基础用法

直接通过 name 来指定图标,例如:

<van-icon name="chat-o" />

本例中, chat-o 是Vant图标的名称。Vant通过名字来找到图标。

使用图片URL:

仍然是使用 name 来指定图标,只不过指向的是一个图标文件,例如:

<van-icon name="https://fastly.jsdelivr.net/npm/@vant/assets/icon-demo.png" />

自定义图标

引入第三方iconfont对应的字体文件和CSS文件,之后就可以在Icon组件中直接使用。例如下面的CSS文件:

css">/* 引入第三方或自定义的字体图标样式 */
@font-face {font-family: 'my-icon';src: url('./my-icon.ttf') format('truetype');
}.my-icon {font-family: 'my-icon';
}.my-icon-extra::before {content: '\e626';
}

注: .ttf (TrueType Font)是一种字体格式。可以通过 @font-face 规则将 .ttf 字体文件嵌入到网页中,使网页能够显示自定义字体。

一种常见用法是利用 .ttf 文件来存储图标:将图标转换为字体的字形,作为字体的字符存储在 .ttf 文件中,并为每个图标分配一个唯一的字符编码(通常使用Unicode编码)。例如:第一个图标放在 \e001 位置,第二个图标放在 \e002 位置,以此类推。

::before 是一个CSS伪元素,它允许你在一个元素的内容之前插入生成的内容。这个生成的内容是虚拟的,不会出现在HTML结构中,而是通过CSS动态添加。本例中, content: '\e626'; 表示在 .my-icon-extra 类的元素内容的前面插入一个字符,其Unicode编码为 \e626

接下来,就可以在页面上使用图标了:

<!-- 通过 class-prefix 指定类名为 my-icon -->
<van-icon class-prefix="my-icon" name="extra" />

这样,就会在该处插入指定的自定义字符,其编码为 \e626 (实际上是一个图标)。

事实上,Vant图标也是一种自定义字符。它的工作原理与上述做法类似。

问题

前面讲的都是Vant图标的用法和原理。但是问题在于,使用Vant组件,虽然能在页面上显示各种图标,还能添加一些效果(比如颜色,右上角显示小红点,等等,本质是对字体的装饰),但是貌似没有简单的方法能获取图标文件(比如 xxx.png )。

例如,在Vant网站上,只有 icon-demo 这个图标可以通过点击右键,保存为png文件:

在这里插入图片描述

这是因为它使用了图片URL,它本来就是一个图标文件。而别的图标是字体,貌似没法直接下载成图标文件。

页面上无法下载图片,那在浏览器里看看页面的源码吧。如下:

在这里插入图片描述

上图中,页面中的 chat-o 图标对应的源码如下:

<i class="van-badge__wrapper van-icon van-icon-chat-o"><!----><!----><!----></i>

正如前面所述,它的原理是CSS找到类,然后通过 ::before 伪元素在此处添加指定的自定义字符(即图标)。所以从页面的源码也看不到图标的具体信息。

我试着自己搭了一个Vant页面环境,效果也是一样的:

  • 页面上无法下载图标文件
  • HTML源码里没有图标的链接或其它信息

分析

既然页面上没办法,那就从Vant的源码着手吧。

安装Vant组件:

npm i vant

注:安装的是 vant ,不是 @vant/weapp ,后者是微信小程序版。

安装好以后,可以发现 node_modules 目录下多了 vant 目录(以及其它几个目录)。其中, node_modules\vant\es\icon\index.css (以及 node_modules\vant\lib\icon\index.css )这个文件大小为45KB:

在这里插入图片描述

一个CSS文件这么大,显然是包含了图标的内容在里面。于是就从这里想办法,把图标搞出来。

打开这个CSS文件,其内容有两个关键点:

  • .van-icon-arrow-double-left:before{content:"\e653"}.van-icon-arrow-double-right:before{content:"\e654"} ……:前面提到了,这些是指定了类和要添加的字符编码。换句话说,每处就代表了一个图标
  • base64,d09GMg......0AAA== :这是一串很长的编码,代表图标的实际内容,使用了 base64 编码。

所以,我们要做的就是,从这一长串编码里,把每个图标的内容提取出来,保存成图标文件。

过程中经历了不少调试和反复的过程,略过不表,只说最后总结。

步骤

步骤1:解码

把base64编码的内容解码,并保存为相应的字体文件格式(本例中为 woff2 )。

注: .woff2 (Web Open Font Format 2.0)也是一种字体文件格式,此处我们不做深究,认为它和 .ttf 文件类似就行了。

有很多方法可以对base64编码做解码。例如,使用Python实现:

python">import base64def main():# 这里是一个示例的 base64 编码的字体数据,你可以从 CSS 文件中提取相应的 base64 字符串替换它base64_font_data = "d09GMg......0AAA==" # 该编码非常长# 解码 base64 数据font_data = base64.b64decode(base64_font_data)# 将解码后的数据保存为 woff2 文件with open('font.woff2', 'wb') as f:f.write(font_data)if __name__ == "__main__":main()

运行代码,生成 font.woff2 文件。

步骤2:提取图标

接下来就要从该文件中提取出字符,也就是图标,然后保存成文件。

我使用了Python来实现。在做之前,需要先安装几个Python包:

pip install fontToolspip install pillowpip install brotli

\e653 为例,代码如下:

python">from fontTools.ttLib import TTFont
from PIL import Image, ImageFont, ImageDraw
import iodef main():# 打开字体文件,这里假设你已经将 base64 编码的字体文件解码为 woff2 并保存为 font.woff2font = TTFont('font.woff2')# 获取字体对象cmap = font.getBestCmap()# 获取 \uE653 对应的 glyph 名称glyph_name = cmap.get(0xE653)if glyph_name:# 获取字形信息glyph = font['glyf'][glyph_name]# 创建一个新的图像image = Image.new('RGBA', (200, 200), (255, 255, 255, 0))draw = ImageDraw.Draw(image)font_obj = ImageFont.truetype('font.woff2', 150)# 绘制字符draw.text((0, 0), chr(0xE653), font=font_obj, fill=(0, 0, 0))# 保存图像image.save('icon_e653.png', 'PNG')if __name__ == "__main__":main()

运行代码,就把 \e653 所指向的图标保存为 icon_e653.png 了。

在这里插入图片描述

同理,把 \e653 换成 \e654 ,就提取了另一个图标:

在这里插入图片描述

步骤3:批量提取图标

要想批量提取图标,只需通过正则表达式来解析 index.css 文件,获取所有图标的名称与字符编号:

python">import re# 读取 CSS 文件
with open('index.css', 'r', encoding='utf-8') as file:css_data = file.read()# 去除前面的无用信息
css_data = re.findall(r'(.*){display:inline-block}(.*)', css_data)[0][1]# 使用正则表达式查找所有图标的 Unicode 编码
unicode_icons = re.findall(r'\.(.*?):before{content:\s*"\\(.*?)"', css_data)# 打印所有找到的 Unicode 编码
for icon in unicode_icons:print(icon)

注:简单分析一下正则表达式。

  • css_data = re.findall(r'(.*){display:inline-block}(.*)', css_data)[0][1]
    • re.findall() 函数:第1个参数是正则表达式,第2个参数是要处理的字符串。该函数在字符串中查找所有匹配正则表达式的子串,并将结果以列表的形式返回。
    • (.*){display:inline-block}(.*) :这是一个正则表达式,其中圆括号括起来的部分是要提取的,本例中有两处圆括号,所以列表中的每个元素会包含两个元素。
    • 本例中,列表只包含一个元素,而该元素(也是个列表)中,我们只需要第二个元素,所以使用了 [0][1] 。本质上,是以 {display:inline-block} 为分隔符,保留分隔符之后的内容。
  • re.findall(r'\.(.*?):before{content:\s*"\\(.*?)"', css_data)
    • \.. 是特殊字符,需要通过 \ 来转义
    • .*? :与 .* 的区别在于,它是非贪婪的
    • \s*\s 表示任何空白字符(如空格、Tab、回车等),后面的 * 表示0次或者多次
    • \\\ 是特殊字符,需要通过 \ 来转义

运行结果如下:

('van-icon-arrow-double-left', 'e653')
('van-icon-arrow-double-right', 'e654')
('van-icon-contact', 'e753')
......
('van-icon-discount-o', 'e6ab')
('van-icon-ecard-pay', 'e6ac')
('van-icon-envelop-o', 'e6ae')

这样,我们就得到了每个图标的名称和编号,接下来,只需和之前的代码结合起来,就可以批量提取图标文件了。

完整代码和用法

把上面的步骤结合起来,形成完整的代码:

python">import base64
import os
from pathlib import Path
import re
from fontTools.ttLib import TTFont
from PIL import Image, ImageFont, ImageDraw
import io# 读取 CSS 文件
css_file = 'index.css'
with open(css_file, 'r', encoding='utf-8') as file:css_data = file.read()# 去除前面的无用信息
css_data = re.findall(r'(.*){display:inline-block}(.*)', css_data)[0][1]# 使用正则表达式查找所有图标的 Unicode 编码
unicode_icons = re.findall(r'\.(.*?):before{content:\s*"\\(.*?)"', css_data)# base64编码
base64_font_data = re.findall(r'(.*?)base64,(.*?)\)(.*)', css_data)[0][1]
# print(base64_font_data)# 解码 base64 数据
font_data = base64.b64decode(base64_font_data)# 将解码后的数据保存为 woff2 文件
woff2_file = 'font.woff2'
with open(woff2_file, 'wb') as f:f.write(font_data)# 打开字体文件,这里假设你已经将 base64 编码的字体文件解码为 woff2 并保存为 font.woff2
font = TTFont(woff2_file)# 获取字体对象
cmap = font.getBestCmap()# 创建目标文件夹
output_dir = 'output'
if not os.path.exists(output_dir):os.makedirs(output_dir)# 批量提取图标
for icon in unicode_icons:# print(icon)# 获取 icon 对应的 glyph 名称glyph_name = cmap.get(int(icon[1], 16))if glyph_name:# 获取字形信息# glyph = font['glyf'][glyph_name]# 创建一个新的图像image = Image.new('RGBA', (200, 200), (255, 255, 255, 0))draw = ImageDraw.Draw(image)font_obj = ImageFont.truetype(woff2_file, 150)# 绘制字符draw.text((0, 0), chr(int(icon[1], 16)), font=font_obj, fill=(0, 0, 0)) # 通过fill参数指定颜色# 保存图像image.save(Path(output_dir) / (icon[0] + '.png'), 'PNG')

用法:保存代码为 test1.py 文件,需要同目录下有 index.css 文件。

在这里插入图片描述

运行代码,就会在当前目录下生成 font.woff2 文件和 output 目录。

在这里插入图片描述

打开 output 目录,里面存放了所有提取出的图标文件。

在这里插入图片描述

注意:在绘制图像( draw.text() 函数)时,使用的颜色是黑色( fill=(0, 0, 0) )。如果希望改变图标的颜色,只需改变颜色的RGB值。

例如,想要生成蓝色图标,则需传入 fill=(0, 0, 255) ,效果如下:

在这里插入图片描述

其它颜色也同理。

总结

  • Vant图标是一种自定义字体,每个图标对应一个字符编号。
  • 图标的显示方式为,在HTML结构里指定类,在CSS文件里针对类,通过伪元素 ::before 在页面插入指定字符(即图标)。
  • 使用这种方法,在页面上以及页面源码里无法直接提取图标文件。
  • 每个字符(即图标)的编号以及全部内容(base64编码)可以从 index.css 获取。
  • 可以自己写代码,从 index.css 里提取图标,具体步骤为:
    1. 获取所有图标的名称(如 van-icon-arrow-double-left ,其实是CSS的类名称)和编号(如 e653
    2. 解码base64数据
    3. 根据以上信息,将每个字符(即图标)绘制出来
    4. 绘制图像时,可以指定颜色
    5. 将绘制的图像保存为图标文件

具体实现,参见上面的Python代码。

参考

  • https://vant-ui.github.io/vant/#/zh-CN/ / https://vant-ui.github.io/vant-weapp/
  • https://vant-ui.github.io/vant/#/zh-CN/icon / https://vant-ui.github.io/vant-weapp/#/icon
  • https://github.com/vant-ui/vant-demo
  • https://github.com/youzan/vant

http://www.ppmy.cn/server/158498.html

相关文章

深入解析 ZooKeeper:分布式协调服务的原理与应用

1.说说 Zookeeper 是什么&#xff1f; ZooKeeper 是一个开源的分布式协调服务&#xff0c;由 Apache Software Foundation 开发维护。它为构建分布式应用程序提供了一套简单且高效的协调接口。ZooKeeper 的设计目的是为了简化分布式系统中常见的任务&#xff0c;例如命名、配置…

【centos】校时服务创建

在 CentOS 下安装校时服务客户端&#xff08;NTP 客户端&#xff09;可以按照以下步骤进行&#xff1a; 安装 NTP 软件包&#xff1a; 打开终端并运行以下命令来安装 NTP 客户端&#xff1a; sudo yum install ntp 启动 NTP 服务&#xff1a; 安装完成后&#xff0c;启动 NT…

Js实现textarea根据字数自动调整高度

目的 我们在前端页面经常遇到需要根据字数的多少来自动调整textarea文本框的高度。 实现 根据字数调整文本框展示的高度&#xff1a; adjustTextareaHeight: function (textarea) {textarea.style.height auto; // 去除之前的高度限制console.log(Math.min(textarea.scrollH…

Rust 正则表达式完全指南

Rust 正则表达式完全指南 Rust通过 regex crate提供正则表达式支持。本指南将详细介绍Rust中正则表达式的使用方法、性能优化和最佳实践。 1. 基础知识 1.1 添加依赖 在 Cargo.toml 中添加&#xff1a; [dependencies] regex "1.10.2"1.2 基本使用 use regex:…

什么是共模电感_共模电感的特性

共模电感&#xff08;Common Mode Inductor&#xff09;&#xff0c;也称为共模扼流圈&#xff0c;是一种特殊的电感器件&#xff0c;主要用于抑制共模噪声&#xff08;Common Mode Noise&#xff09;。以下是关于共模电感的详细解释及其特性&#xff1a; 定义 共模电感是一种…

Oracle概述

Oracle概述 Oracle是世界领先的信息管理软件开发商,因其复杂的关系数据库产品而闻名。以下是对Oracle的详细介绍: 一、公司背景与概况 成立时间与地点 :Oracle公司成立于1977年,总部位于美国加州红木滩市(Redwood Shores)。创始人 :由劳伦斯埃里森(Lawrence J. Ellis…

Linux SUID提权

文章目录 1. SUID/SGID2. 查找SUID文件3. SUID/SGID提权3.1 SUID配置不当3.2 SUID systemctl提权3.3 $PATH变量劫持 参考 1. SUID/SGID SUID&#xff08;Set User ID&#xff09;意味着如果某个用户对属于自己的文件设置了这种权限&#xff0c;那么其他用户在执行这一脚本时也…

蓝桥杯历届真题 #分布式队列 (Java,C++)

文章目录 题目解读[蓝桥杯 2024 省 Java B] 分布式队列题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 思路完整代码 题目解读 题目链接 [蓝桥杯 2024 省 Java B] 分布式队列 题目描述 小蓝最近学习了一种神奇的队列&#xff1a;分布式队列。简单来说&#x…