实现一个颜色吸管
发表于:2023-12-22 |

前言

写这篇文章就是突发奇想了,我也不知道咋回事就想着研究下这个怎么实现,哈哈。

获取页面中所有颜色

我们先来最简单的,获取页面中所有dom的颜色,这里我使用了一个方法extractColors,这个方法通过getComputedStyle得到元素的计算样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<script setup>
import { onMounted, ref } from "vue";

const colorList=ref([])
function extractColors() {
const elements = document.querySelectorAll("*"); // 获取页面中的所有元素

const colors = new Set(); // 使用 Set 数据结构保存唯一的颜色值

for (let i = 0; i < elements.length; i++) {
const element = elements[i];

const computedStyle = getComputedStyle(element); // 获取元素的计算样式

// 提取背景颜色和文本颜色
const backgroundColor = computedStyle.backgroundColor;
const textColor = computedStyle.color;

// 将颜色添加到集合中
colors.add(backgroundColor);
colors.add(textColor);
}

// 将颜色转换为数组并返回
return Array.from(colors);
}

onMounted(() => {
// 调用函数提取颜色
const colors = extractColors();

// 打印提取到的颜色
console.log(colors);
colorList.value=colors
})

</script>

<template>
<div class="red block">
我是一个100*100的红色块
</div>
<div class="green block">
我是一个100*100的绿色块
</div>
<div v-if="colorList&&colorList.length>0">
我是提取到的颜色:
<div v-for="color in colorList" :style="{backgroundColor:color}" class="block">{{color}}</div>
</div>
</template>
<style>
.block{
width: 100px;
height: 100px;
}
.red{
background-color: red;
}
.green{
background-color: green;
}
</style>

效果图

通过点击的方式得到页面中的颜色

接下来,我们改写一下,加入点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script setup>
import { ref } from "vue";
const colorList=ref([])
function handleClick(event) {
const target = event.target;
console.log(target)
// 获取元素颜色
const elementColor = getComputedStyle(target).backgroundColor;
console.log("Element color:", elementColor);
colorList.value.push(elementColor)
}
// 绑定点击事件处理函数到 document 上
document.addEventListener("click", handleClick);

</script>

<template>
<div class="red block">
我是一个100*100的红色块
</div>
<div class="green block">
我是一个100*100的绿色块
</div>
<div v-if="colorList&&colorList.length>0">
我是提取到的颜色:
<div v-for="color in colorList" :style="{backgroundColor:color}" class="block">{{color}}</div>
</div>
</template>
<style>
.block{
width: 100px;
height: 100px;
}
.red{
background-color: red;
}
.green{
background-color: green;
}
</style>

此时效果就是这样了

优化点击获取

就这样的话其实也有问题,那就是我们明明是点击的文字,但是显示的依旧是外层包裹的颜色,这样是不符合我们的需求的,所以继续优化一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<script setup>
import { ref,onMounted } from "vue";
const colorList=ref([])
// 生成随机数
const generateRandomHexColor = () => `ContentText${Math.floor(Math.random() * 0xffffff).toString(16)}`;
// 判断dom中是否有文字
function hasTextContent(element) {
return element && element.textContent && element.textContent.trim() !== '';
}

// 判断dom子节点是否是文本节点
function hasSingleTextNodeChild(element) {
return element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE;
}
function handleClick(event) {
const target = event.target;
if(target.id.includes('ContentText')){
// 获取这个dom文本的颜色
const computedStyle = getComputedStyle(target); // 获取元素的计算样式
const color = computedStyle.color; // 获取计算样式中的颜色
colorList.value.push(color)
}
else{
const computedStyle = getComputedStyle(target); // 获取元素的计算样式
const color = computedStyle.backgroundColor; // 获取计算样式中的颜色
colorList.value.push(color)
}
}

// 给所有内部是只有文本的节点加一个span
function addSpanToText(){
// 获取所有节点
const element = document.querySelectorAll('body *');
element.forEach((item)=>{
// 判断dom子节点是否是文本节点
if(item.nodeType === Node.ELEMENT_NODE&&hasTextContent(item)&&hasSingleTextNodeChild(item)){
// 插入span标签
const span=document.createElement('span')
span.id=generateRandomHexColor()
span.textContent=item.textContent
item.textContent=''
item.appendChild(span)
}
})
}

// 绑定点击事件处理函数到 document 上
document.addEventListener("click", handleClick);

onMounted(()=>{
addSpanToText()
})

</script>

<template>
<div class="inline-block">
<div class="red block">
我是一个100*100的红色块
</div>
<div class="green block">
我是一个100*100的绿色块
</div>
<div class="blue block"></div>
</div>
<div v-if="colorList&&colorList.length>0">
我是提取到的颜色:
<div v-for="color in colorList" :style="{backgroundColor:color}" class="block">{{color}}</div>
</div>
</template>
<style>
.inline-block{
display: inline-block;
}
.block{
width: 100px;
height: 100px;
}
.red{
background-color: red;
}
.green{
background-color: green;
}
.blue{
background-color: blue;
}
</style>

这里我的实现思路是给那种只有一个子节点,并且子节点是文本节点且有内容的dom中的这个文本节点加一个随机id的span,而这个span我加了一个专属的前缀ContentText,这样我们就可以通过这个前缀来判断这个dom是不是我们加的span,如果是的话,就获取这个span中的文本的颜色,如果不是的话,就获取这个dom的背景颜色,这样就可以实现我们的需求了。

获取图片中的颜色

在事件处理函数中,我们首先获取了点击位置的坐标 offsetX 和 offsetY。接下来,我们创建一个临时的 <canvas> 元素,并使用 drawImage 方法将图像绘制在其中。然后通过canvas的getImageData方法获取点击位置的像素数据,最后将像素数据转换为颜色值。这里需要注意一点就是这个图片如果是跨域的必须要服务器支持跨域,不然会报错,这里我使用的是本地图片,所以就不用考虑这个问题了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<script setup>
import { ref,onMounted } from "vue";
const colorList=ref([])
// 生成随机数
const generateRandomHexColor = () => `ContentText${Math.floor(Math.random() * 0xffffff).toString(16)}`;
// 判断dom中是否有文字
function hasTextContent(element) {
return element && element.textContent && element.textContent.trim() !== '';
}

// 判断是否是文本节点
function hasSingleTextNodeChild(element) {
return element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE;
}
function handleClick(event) {
const target = event.target;
if(target.id.includes('ContentText')){
// 获取这个dom文本的颜色
const computedStyle = getComputedStyle(target); // 获取元素的计算样式
const color = computedStyle.color; // 获取计算样式中的颜色
colorList.value.push(color)
}
// 判断是不是图像节点
else if(target.nodeType===Node.ELEMENT_NODE&&target.tagName==='IMG'){
// 获取点击的坐标
const x = event.offsetX;
const y = event.offsetY;

// 创建一个临时 canvas 元素并绘制图像
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
let img = new Image();
img.src = target.src;
// img.crossOrigin = "anonymous"; // 图片允许跨域,否则不能拿到图片的数据
img.onload=()=>{
context.drawImage(target, 0, 0,target.width,target.height);
// 获取点击位置的像素数据
const pixelData = context.getImageData(x, y, 1, 1).data;

// 将像素数据转换为颜色值
const color = 'rgb(' + pixelData[0] + ', ' + pixelData[1] + ', ' + pixelData[2] + ')';

colorList.value.push(color)
}
img.onerror=(err)=>{
console.log(err)
}

}
else{
const computedStyle = getComputedStyle(target); // 获取元素的计算样式
const color = computedStyle.backgroundColor; // 获取计算样式中的颜色
colorList.value.push(color)
}
}

// 给所有内部是只有文本的节点加一个span
function addSpanToText(){
// 获取所有节点
const element = document.querySelectorAll('body *');
// 判断是否是文本节点
element.forEach((item)=>{
if(item.nodeType === Node.ELEMENT_NODE&&hasTextContent(item)&&hasSingleTextNodeChild(item)){
// 插入span标签
const span=document.createElement('span')
span.id=generateRandomHexColor()
span.textContent=item.textContent
item.textContent=''
item.appendChild(span)
}
})
}

// 绑定点击事件处理函数到 document 上
document.addEventListener("click", handleClick);

// 调用函数提取图片颜色
const imageUrl =new URL("./assets/avatar.jpg", import.meta.url).href;
// const imageUrl=ref('https://myblog-5g89ixpbbf1fbfad-1316695488.ap-shanghai.app.tcloudbase.com/img/avatar.jpg')

onMounted(()=>{
addSpanToText()
})

</script>

<template>
<div class="inline-block">
<div class="red block">
我是一个100*100的红色块
</div>
<div class="green block">
我是一个100*100的绿色块
</div>
<div class="blue block"></div>
<img :src="imageUrl" alt="" width="100">
</div>
<div v-if="colorList&&colorList.length>0">
我是提取到的颜色:
<div v-for="color in colorList" :style="{backgroundColor:color}" class="block">{{color}}</div>
</div>
</template>
<style>
.inline-block{
display: inline-block;
}
.block{
width: 100px;
height: 100px;
}
.red{
background-color: red;
}
.green{
background-color: green;
}
.blue{
background-color: blue;
}
</style>

结语

这样,我们提取颜色的逻辑就差不多完成了,后续大家有兴趣就可以自己扩展了,比如说提取页面中的图片颜色,大家就可以让用户自己上传图片,然后你就可以支持获取这张图片中的颜色,又比如让用户输入一个页面地址,你就可以通过脚本注入获取iframe中的颜色,总之,这个功能的扩展性还是很强的,大家可以自己去发挥,更多内容敬请期待,债见~

上一篇:
新版Cesium加载地形terrainProvider报错
下一篇:
浅浅玩一下mark.js