被领导表扬过的蓝湖主题色插件,你确定不来看看吗?
背景
公司最近招募了一批实习生,经过一个月的集中培训后,他们开始接手项目。第一个项目是对应用程序页面样式进行重构,因为之前的样式不统一且不够美观,希望实习生在修改样式的同时熟悉业务流程
在代码审查时,我发现大部分同事直接使用了色值,而没有采用统一的主题色名称。我询问了原因,得到的回答是“写着写着漏掉了”。回想起来,我自己也曾遇到过类似的情况。这让我思考,是否可以在蓝湖上提供一些提示
我与UI讨论过这个问题,但他们没有提出有效的解决方案。我曾经使用过一个翻译浏览器插件,当我选中不认识的英文单词时,它会自动提供中文提示。这让我想到,是否可以借鉴这个思路,开发一个类似的浏览器插件来解决我们的问题?
分析
我们平时使用蓝湖的习惯是,比如在进行按钮样式修改之前,先用鼠标点击UI图上的按钮。这时,侧边会弹出一个样式信息框(这是蓝湖自带的功能),里面显示了该按钮的相关样式,包括宽高、字体、边距和颜色等信息

设置主题色色值和名称
计划用插件中的popup弹框做,另外配合chrome.storage
API 存储和读取数据
监听DOM变化
经过观察得到每一次点击UI上的样式都会触发 DOM 更新,可以使用 JavaScript 中的 MutationObserver
来监听 DOM 的变化
找到当前样式的颜色值
通过样式信息里面的颜色,拿到颜色值
提示主题色信息
获取到颜色值后,判断其是否符合主题色。如果符合,则提示相应的主题色信息
代码实现
popup弹框
需要提前设置好主题色色值和主题色名称,点击插件图标会弹出设置面板
// popup.html
<body>
<h3>请设置主题色</h3>
<div id="container"></div>
<div class="btn-box">
<button id="addBtn">添加</button>
<button id="submitBtn">保存</button>
<button id="clearBtn">清空</button>
</div>
<script type="text/javascript" src="./scripts/popup.js"></script>
</body>
// popup.js
let containerEl = document.getElementById('container');
let addBtn = document.getElementById('addBtn');
let clearBtn = document.getElementById('clearBtn');
let submitBtn = document.getElementById('submitBtn');
// 添加
addBtn.addEventListener('click', addElement)
// 清除
clearBtn.addEventListener('click', () => {
chrome.storage.sync.remove('colorData');
let inputDivArr = [...document.getElementsByClassName('input-box')];
inputDivArr.forEach(inputDivEl => {
let inputArr = inputDivEl.querySelectorAll('input')
inputArr[0].value = ''
inputArr[1].value = ''
})
})
function addElement() {
let newDiv = document.createElement('div')
let newColorInput = document.createElement('input')
let newNameInput = document.createElement('input')
newDiv.setAttribute('class', 'input-box')
newColorInput.setAttribute('placeholder', '请输入色值')
newNameInput.setAttribute('placeholder', '请输入主题色名称')
newDiv.appendChild(newColorInput)
newDiv.appendChild(newNameInput)
containerEl.appendChild(newDiv)
}
// 保存
submitBtn.addEventListener('click', () => {
let inputDivArr = [...document.getElementsByClassName('input-box')];
let data = inputDivArr.map(inputDivEl => {
let inputArr = inputDivEl.querySelectorAll('input')
return {
name: inputArr[1].value,
color: inputArr[0].value,
}
}).filter(item => item.color !== '' || item.name !== '');
chrome.storage.sync.set({colorData: data}, function() {
window.close();
});
})
// 回显
document.addEventListener('DOMContentLoaded', () => {
chrome.storage.sync.get(['colorData'], (result) => {
if(result.colorData && Array.isArray(result.colorData)) {
result.colorData.forEach(item => {
let newDiv = document.createElement('div')
let newColorInput = document.createElement('input')
let newNameInput = document.createElement('input')
newDiv.setAttribute('class', 'input-box')
newColorInput.setAttribute('placeholder', '请输入色值')
newColorInput.setAttribute('value', item.color)
newNameInput.setAttribute('placeholder', '请输入主题色名称')
newNameInput.setAttribute('value', item.name)
newDiv.appendChild(newColorInput)
newDiv.appendChild(newNameInput)
containerEl.appendChild(newDiv)
});
for(let i = 0; i < 5 - result.colorData.length; i++) {
addElement()
}
} else {
for(let i = 0; i < 5; i++) {
addElement()
}
}
});
})
插件主体
插件主体业务代码依赖于MutationObserver
API 监测网页 DOM 的变化,并根据变化动态更新某些元素的内容和样式
const observer = new MutationObserver(function (mutationsList, observer) {
// 在每次变化发生时要执行的操作
var elements = document.getElementsByClassName(' mu-drawer open');
if (elements.length > 0) {
elements = document.getElementsByClassName('layer_color');
if (elements.length > 0) {
for (let key = 0; key < elements.length; key++) {
let container = elements[key];
let themeElements = container.getElementsByClassName('el-theme');
let themeElement = themeElements.length > 0 ? themeElements[0] : null;
if (!themeElement) {
themeElement = document.createElement('div');
themeElement.className = 'el-theme';
themeElement.style.color = '#7A7AF9';
themeElement.style.fontWeight = 'bold';
let index = 0;
if (container.getElementsByClassName('subtitle color_title').length > 0) {
index = 1;
}
container.insertBefore(themeElement, container.children[index]);
}
let colorElements = container.getElementsByClassName("color_li");
if (colorElements.length > 0) {
let colors = [];
for (let i = 0; i < colorElements.length; i++) {
let ele = colorElements[i];
colors.push(getColor(ele));
}
// 单颜色处理,目前没有处理混合颜色
if (colors.length == 1) {
let color = colors[0];
chrome.storage.sync.get(['colorData'], (result) => {
let dataItem = result.colorData.find(item => item.color.toUpperCase() == color.toUpperCase())
if(typeof dataItem == 'object') {
themeElement.style.display = 'block';
themeElement.textContent = dataItem.name;
} else {
themeElement.style.display = 'none';
themeElement.textContent = ''
}
});
}
else {
themeElement.style.display = 'none';
themeElement.textContent = ''
}
}
}
}
}
});
const rgb = (r, g, b) => {
return `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`.toLocaleLowerCase();
}
const getColor = (element) => {
let colors = element.getElementsByClassName('color');
if (colors.length == 1) {
let color = colors[0];
let background = color.style.background;
if (background) {
background = background.replace(/\s/g, '');
if (background.startsWith('rgb')) {
let colorint = background.replace('rgb(', '').replace(')', '').split(',');
return rgb(Number(colorint[0]), Number(colorint[1]), Number(colorint[2]));
}
}
}
}
// 开始监测指定节点的变化
observer.observe(document.body, {
childList: true, // 监测子节点的变化
attributes: true, // 监测属性的变化
subtree: true, // 包括所有后代节点
attributeFilter: ['class', 'style'] // 过滤属性,只监测指定的属性变化
});
其思路是获取样式信息的弹出框 DOM 元素,然后从中提取颜色信息的 DOM 元素。接着,动态创建一个新的 DOM 元素用于展示主题色名称。如果存在已设置的主题色,则显示相应的主题色名称。目前不支持混合颜色

总结
大家感觉是不是挺有意思?整个实现过程并不复杂,有兴趣的同学可以一起交流交流
如果遇到类似的问题,欢迎使用这个插件,如果你喜欢它,欢迎star🚗🚗🚗
转载自:https://juejin.cn/post/7394835461040504851