为了找一个close图标,最后手搓了一个图标管理页面

前戏

正如标题所诉,当时在开发一个小demo时,需要一个close图标(也就是一个叉叉)。然后纠结症就犯了:

  1. 手搓一个CSS图标?也就几行css代码的事儿,通过伪元素::before::after这样这样,然后这样,就行了;
  2. 还是去阿里的字体图标库走一遭儿?就一个小demo好像也没啥必要;
  3. 或者去图标库找一个差不多的copy一个svg代码?去哪个图标网站好呢?

打住打住!有这纠结的时间,一个叉叉早就搓出来了。

思考

于是乎,一顿机械化的操作,一个正儿八经的close图标就完成了。我想这种代码作为一个老前端人估计都要写烂了吧。relative...absolute...content...top...transform...rotate...

虽然不难,但这种重复性的代码写起来实在恶心。以前写过无数遍,如果能直接copy就好了,可是,一下子又不知上哪去copy。

如果有一个集中管理的地方就好了,需要的时候直接拿来就用,没有的图标也能够快速放上去以便下次使用。好了,几个重点:

  1. 集中管理;
  2. 拿来就用;
  3. 快速更新;

集中管理,那就做一个属于自己的图标管理页面,绑定好域名,需要的时候“唰”的一下就找到了。

拿来就用,页面上需要展示所有的图标,以及每个图标对应的代码。

至于快速更新,从纯前端出发,肯定是丢一段图标的css代码上去就行了。实现嘛?好像有点难度……

渲染图标

先在脑子里画一个草图:

图标管理页面草图

这种简单的页面,也不需要啥啥全家桶了,直接用原始前端三件套来搞吧。

先写一个html架子,大概就这样吧:

1
2
3
4
5
6
7
8
9
10
11
<div class="layout">
<main>
<div class="icon-container"></div>
</main>
<aside>
<div class="icon-info"></div>
<div class="code html-code"></div>
<div class="code css-code"></div>
<div class="code common-code"></div>
</aside>
</div>

然后用css来简单调一下样式,就不贴代码了。

最后我们来复习一下原生DOM操作的API吧,还记得你多久没写原生DOM操作了吗?

我们首先要定义一个图标数组,里面放图标的类名icon-close

1
const cssIcons = ['icon-close']; // 好吧,现在就这一个图标

遍历数组,生成图标DOM,需要用到document.createElementAPI:

1
2
3
4
5
6
7
8
9
10
11
const iconFragment = document.createDocumentFragment();
cssIcons.forEach((icon, index) => {
const iconElement = document.createElement('i');
iconElement.classList.add('css-icon', icon);
const iconItem = document.createElement('div');
iconItem.classList.add('icon-item');
iconItem.setAttribute('data-name', icon);

iconItem.appendChild(iconElement);
iconFragment.appendChild(iconItem);
});

他们说,对于频繁操作DOM的时候,要先创建一个DocumentFragment,然后再一次性添加到文档中。还记得这个前端优化点吧?尽量减少DOM的操作

再将DocumentFragment添加到DOM文档中去:

1
2
const iconContainer = document.querySelector('.icon-container');
iconContainer.appendChild(iconFragment);

交互

交互很简单,就是点击图标的时候,右侧边栏显示对应的图标信息和代码。

首先,肯定是绑定事件了。好,考点又来了:对于一个动态的列表,我们的监听事件应该放在哪里比较好?

老前端人都知道,肯定放外面的容器上了!

1
2
3
4
5
6
7
8
9
10
11
iconContainer.addEventListener('click', e => {
if (e.target.classList.contains('.icon-item')) {
if (lastActiveIcon) {
lastActiveIcon.classList.remove('active');
}
e.target.classList.add('active');
lastActiveIcon = e.target;

// 渲染右侧图标代码
}
});

首先说一个小小的优化点:我们每次点击图标的时候,都需要将之前的选中状态清掉,最直接的方法可能是遍历,我这里用了一个变量lastActiveIcon来记录上一次选中的图标,因为每次有且只有一个图标能被选中。

其实上面的事件委托的写法还存在一个问题,就是当我们点击的是.icon-item的子元素时,e.target并不是我们需要的DOM元素。一开始我想到了用.icon-item * { point-events: none; }将所有的子元素的点击事件都禁止掉,但这样总感觉不够优雅。

后来,在AI的帮助下,发现了一个APIclosest(好吧,我承认自己没有印象了),这玩意儿用在这个场景是最合适不过了。于是就改了一下上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
iconContainer.addEventListener('click', e => {
const iconItem = e.target.closest('.icon-item');
if (iconItem) {
if (lastActiveIcon) {
lastActiveIcon.classList.remove('active');
}
iconItem.classList.add('active');
lastActiveIcon = iconItem;

// 渲染右侧图标代码
}
});

完美解决!

渲染图标代码

图标渲染比较简单,首先就是cloneNode一个图标,然后createElement一些必要的信息,最后appendChild到对应的DOM中去。

接下来问题来了,我们怎么渲染图标的代码呢?

html代码相对比较简单,直接通过innerHTML就能拿到,重点是css代码好像有点不好拿。

有点印象的getComputedStyleAPI,可以获取到元素的样式,可是这玩意儿拿到的是全量的样式代码,我们需要的CSS代码肯定是希望与定义时写的保持一致。

运行时好像不太好拿,那能不能在编译阶段就获取呢?理论上肯定是可行的,但这种方案还是作为最后的保留手段吧!

再次拿出AI,发现了一个document.styleSheetsAPI。没用过,去caniuse上查一下,嗯,可以用。

这玩意儿可以拿到DOM上所有的样式表,然后通过遍历可以找到我们需要的样式表。为了寻找方便,我们可以给link设置一个data-name属性,并将我们的CSS图标代码放在icon.css的独立文件中。

1
<link rel="stylesheet" href="/icon.css" data-name="icon">
1
2
3
4
5
6
7
8
9
10
11
12
function getIconStyle(iconName) {
const iconStyleSheet = [...document.styleSheets].find(styleSheet => styleSheet.ownerNode.dataset.name === 'icon');
const iconCssText = [];
const rules = iconStyleSheet.cssRules || iconStyleSheet.rules;
for (let j = 0; j < rules.length; j++) {
const rule = rules[j];
if (rule.selectorText && rule.selectorText.match(iconName)) {
iconCssText.push(rule.cssText);
}
}
return iconCssText.join('\n');
}

很好,CSS代码拿到了,就是格式有点乱,我们再简单的格式化一下吧。也不要用啥格式化工具库了,就几行代码没啥必要。我们观察一下,基本上就是在{};后面换行缩进一下就大差不差了。

1
2
3
4
5
6
7
function cssFormat(cssText) {
return cssText
.replace(/\{\s*/g, '{\n ')
.replace(/;\s*/g, ';\n ')
.replace(/\s*\}/g, '\n}\n')
.replace(/,\s*\./g, ',\n\.')
}

最后提一下,还记得一开始我们定义的图标数组cssIcons吗?既然已经可以拿到图标的样式表了,不如这一步手动定义也省了吧,直接动态生成我们的图标数组:

1
2
3
4
5
6
const iconStyleSheet = [...document.styleSheets].find(styleSheet => styleSheet.ownerNode.dataset.name === 'icon');

const iconClasses = [...(iconStyleSheet.cssRules || iconStyleSheet.rules)]
.filter(rule => rule.selectorText && rule.selectorText.startsWith('.icon-'))
.map(rule => rule.selectorText.slice(1).replace(/::.*/g, ''))
const cssIcons = [...new Set(iconClasses)];

这样,以后添加图标的时候,只需要在icon.css文件中贴上我们的CSS图标代码就搞定了。

最后,去网上扒了一些前端大佬的CSS图标,贴几个上去,让页面看起来不那么单调。

尾声

页面很简单,但完美的实现了一开始的几个需求点。

看一下最后的效果吧:https://demo.codingmo.com/demos/cssIcon/

项目已开源:https://github.com/moohng/demo/tree/main/demos/cssIcon

有任何想法也欢迎大家PR,反正都是玩嘛!

最后,我想说,作为一个老前端搬砖人,在写烦了千篇一律的业务代码时,偶尔给自己找点伪需求。欸,还别说,比起写那些无脑的增删改查还更有意思呢!


为了找一个close图标,最后手搓了一个图标管理页面
https://codingmo.com/article/20241105/9a7ad1cc810c/
作者
颜漠笑年
发布于
2024年11月5日
许可协议