465 lines
12 KiB
HTML
465 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||
<title>Element 风格颜色阶数计算器</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: "Microsoft Yahei", sans-serif;
|
||
}
|
||
|
||
body {
|
||
padding: 2rem;
|
||
background-color: #F5F7FA;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.container {
|
||
background: white;
|
||
padding: 2rem;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.input-group {
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.input-row {
|
||
display: flex;
|
||
gap: 1rem;
|
||
margin-bottom: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.input-item {
|
||
flex: 1;
|
||
min-width: 250px;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
}
|
||
|
||
#baseColor, #varPrefix {
|
||
width: 100%;
|
||
padding: 0.8rem;
|
||
border: 1px solid #DCDFE6;
|
||
border-radius: 4px;
|
||
font-size: 1rem;
|
||
outline: none;
|
||
}
|
||
|
||
#baseColor:focus, #varPrefix:focus {
|
||
border-color: #5D87FF;
|
||
}
|
||
|
||
.options {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.checkbox-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
button {
|
||
background-color: #5D87FF;
|
||
color: white;
|
||
border: none;
|
||
padding: 0.8rem 2rem;
|
||
border-radius: 4px;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
margin-right: 1rem;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #85A5FF;
|
||
}
|
||
|
||
.result-section {
|
||
margin-top: 2rem;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.color-row {
|
||
display: flex;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.color-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 1rem;
|
||
border-radius: 4px;
|
||
border: 1px solid #EEEEEE;
|
||
min-width: 120px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.color-card:hover {
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.color-swatch {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 4px;
|
||
margin-bottom: 0.8rem;
|
||
border: 1px solid #DDDDDD;
|
||
}
|
||
|
||
.color-info {
|
||
text-align: center;
|
||
}
|
||
|
||
.color-name {
|
||
font-weight: 600;
|
||
margin-bottom: 0.3rem;
|
||
color: #333333;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.color-code {
|
||
color: #666666;
|
||
font-family: monospace;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.css-code {
|
||
margin-top: 2rem;
|
||
padding: 1rem;
|
||
background-color: #F8F9FA;
|
||
border-radius: 4px;
|
||
font-family: monospace;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
color: #333333;
|
||
border: 1px solid #EEEEEE;
|
||
position: relative;
|
||
}
|
||
|
||
.copy-btn {
|
||
position: absolute;
|
||
top: 1rem;
|
||
right: 1rem;
|
||
padding: 0.4rem 0.8rem;
|
||
font-size: 0.8rem;
|
||
background-color: #4CAF50;
|
||
border: none;
|
||
color: white;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.copy-btn:hover {
|
||
background-color: #45A049;
|
||
}
|
||
|
||
.tooltip {
|
||
position: fixed;
|
||
padding: 0.5rem 1rem;
|
||
background-color: rgba(0, 0, 0, 0.8);
|
||
color: white;
|
||
border-radius: 4px;
|
||
font-size: 0.9rem;
|
||
z-index: 9999;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
pointer-events: none;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Element 风格颜色阶数计算器</h1>
|
||
<div class="input-group">
|
||
<div class="input-row">
|
||
<div class="input-item">
|
||
<label for="baseColor">输入主色 HEX 值(如 #c93649、#FFCC55):</label>
|
||
<input id="baseColor" placeholder="请输入带 # 的 HEX 色值,例如 #5D87FF" type="text" value="#D4A017">
|
||
</div>
|
||
<div class="input-item">
|
||
<label for="varPrefix">变量前缀(替代 primary):</label>
|
||
<input id="varPrefix" placeholder="例如:warning、danger、custom" type="text" value="warning">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="options">
|
||
<div class="checkbox-item">
|
||
<input id="withHash" checked type="checkbox">
|
||
<label for="withHash">复制色值时包含 # 号</label>
|
||
</div>
|
||
</div>
|
||
|
||
<button onclick="calculateColorLevels()">计算色阶</button>
|
||
<button onclick="copyAllCss()">一键复制所有变量</button>
|
||
</div>
|
||
|
||
<div id="resultSection" class="result-section">
|
||
<!-- 色阶结果会动态生成 -->
|
||
</div>
|
||
|
||
<div id="cssCode" class="css-code">
|
||
<!-- CSS 代码会动态生成 -->
|
||
<button class="copy-btn" onclick="copyAllCss()">复制代码</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 复制提示弹窗 -->
|
||
<div id="tooltip" class="tooltip">复制成功!</div>
|
||
|
||
<script>
|
||
// 全局变量存储当前计算结果
|
||
let currentColorResults = {}
|
||
|
||
/**
|
||
* HEX 转 RGB
|
||
* @param {string} hex 带 # 的 HEX 色值
|
||
* @returns {object} r/g/b 数值(0-255)
|
||
*/
|
||
function hexToRgb(hex) {
|
||
hex = hex.replace(/^#/, '')
|
||
if (hex.length === 3) {
|
||
hex = hex.split('').map(c => c + c).join('')
|
||
}
|
||
|
||
const r = parseInt(hex.substring(0, 2), 16)
|
||
const g = parseInt(hex.substring(2, 4), 16)
|
||
const b = parseInt(hex.substring(4, 6), 16)
|
||
|
||
return { r, g, b }
|
||
}
|
||
|
||
/**
|
||
* RGB 转 HEX
|
||
* @param {number} r 红(0-255)
|
||
* @param {number} g 绿(0-255)
|
||
* @param {number} b 蓝(0-255)
|
||
* @returns {string} 带 # 的 HEX 色值
|
||
*/
|
||
function rgbToHex(r, g, b) {
|
||
r = Math.max(0, Math.min(255, Math.round(r)))
|
||
g = Math.max(0, Math.min(255, Math.round(g)))
|
||
b = Math.max(0, Math.min(255, Math.round(b)))
|
||
|
||
return '#' +
|
||
r.toString(16).padStart(2, '0') +
|
||
g.toString(16).padStart(2, '0') +
|
||
b.toString(16).padStart(2, '0')
|
||
}
|
||
|
||
/**
|
||
* 计算颜色亮度(相对亮度)
|
||
* @param {number} r 红(0-255)
|
||
* @param {number} g 绿(0-255)
|
||
* @param {number} b 蓝(0-255)
|
||
* @returns {number} 亮度值(0-100)
|
||
*/
|
||
function getLuminance(r, g, b) {
|
||
const a = [ r, g, b ].map(v => {
|
||
v /= 255
|
||
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)
|
||
})
|
||
return (a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722) * 100
|
||
}
|
||
|
||
/**
|
||
* 显示复制提示
|
||
* @param {string} text 提示文本
|
||
*/
|
||
function showTooltip(text) {
|
||
const tooltip = document.getElementById('tooltip')
|
||
tooltip.textContent = text
|
||
tooltip.style.opacity = 1
|
||
tooltip.style.left = (window.innerWidth / 2 - tooltip.offsetWidth / 2) + 'px'
|
||
tooltip.style.top = (window.innerHeight / 2) + 'px'
|
||
|
||
setTimeout(() => {
|
||
tooltip.style.opacity = 0
|
||
}, 1500)
|
||
}
|
||
|
||
/**
|
||
* 复制文本到剪贴板
|
||
* @param {string} text 要复制的文本
|
||
*/
|
||
function copyToClipboard(text) {
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
showTooltip('复制成功!')
|
||
}).catch(err => {
|
||
showTooltip('复制失败:' + err)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 核心:计算 Element 风格的颜色阶数
|
||
*/
|
||
function calculateColorLevels() {
|
||
const baseHex = document.getElementById('baseColor').value.trim()
|
||
const varPrefix = document.getElementById('varPrefix').value.trim() || 'primary'
|
||
|
||
// 验证 HEX 格式
|
||
if (!/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(baseHex)) {
|
||
alert('请输入有效的 HEX 色值,例如 #5D87FF 或 #fff')
|
||
return
|
||
}
|
||
|
||
const rgb = hexToRgb(baseHex)
|
||
const { r: r0, g: g0, b: b0 } = rgb
|
||
|
||
// 色阶调整参数
|
||
const levelConfig = {
|
||
'dark-2': { brightness: -0.18, saturation: 0.05 },
|
||
'light-3': { brightness: 0.10, saturation: -0.10 },
|
||
'light-5': { brightness: 0.20, saturation: -0.20 },
|
||
'light-7': { brightness: 0.30, saturation: -0.30 },
|
||
'light-8': { brightness: 0.35, saturation: -0.35 },
|
||
'light-9': { brightness: 0.40, saturation: -0.40 },
|
||
}
|
||
|
||
// 计算各阶颜色
|
||
currentColorResults = {
|
||
'主色': baseHex,
|
||
}
|
||
|
||
// 遍历计算每个阶数
|
||
Object.entries(levelConfig).forEach(([ level, config ]) => {
|
||
const newR = r0 + (255 - r0) * config.brightness
|
||
const newG = g0 + (255 - g0) * config.brightness
|
||
const newB = b0 + (255 - b0) * config.brightness
|
||
|
||
// 饱和度调整
|
||
const gray = (r0 + g0 + b0) / 3
|
||
const finalR = config.saturation > 0
|
||
? newR + (newR - gray) * config.saturation
|
||
: newR - (newR - gray) * Math.abs(config.saturation)
|
||
const finalG = config.saturation > 0
|
||
? newG + (newG - gray) * config.saturation
|
||
: newG - (newG - gray) * Math.abs(config.saturation)
|
||
const finalB = config.saturation > 0
|
||
? newB + (newB - gray) * config.saturation
|
||
: newB - (newB - gray) * Math.abs(config.saturation)
|
||
|
||
// 转 HEX 并保存
|
||
currentColorResults[level] = rgbToHex(finalR, finalG, finalB).toUpperCase()
|
||
})
|
||
|
||
// 渲染结果
|
||
renderResults(currentColorResults, varPrefix)
|
||
// 生成 CSS 代码
|
||
generateCssCode(currentColorResults, varPrefix)
|
||
}
|
||
|
||
/**
|
||
* 渲染颜色结果卡片(横向排列)
|
||
*/
|
||
function renderResults(results, varPrefix) {
|
||
const resultSection = document.getElementById('resultSection')
|
||
resultSection.innerHTML = ''
|
||
|
||
// 创建横向容器
|
||
const colorRow = document.createElement('div')
|
||
colorRow.className = 'color-row'
|
||
|
||
// 按固定顺序展示
|
||
const displayOrder = [ '主色', 'dark-2', 'light-3', 'light-5', 'light-7', 'light-8', 'light-9' ]
|
||
|
||
displayOrder.forEach(level => {
|
||
const hex = results[level]
|
||
const card = document.createElement('div')
|
||
card.className = 'color-card'
|
||
card.title = '点击复制色值'
|
||
|
||
// 颜色块
|
||
const swatch = document.createElement('div')
|
||
swatch.className = 'color-swatch'
|
||
swatch.style.backgroundColor = hex
|
||
|
||
// 颜色信息
|
||
const info = document.createElement('div')
|
||
info.className = 'color-info'
|
||
|
||
// 变量名
|
||
const varName = level === '主色'
|
||
? `--el-color-${varPrefix}`
|
||
: `--el-color-${varPrefix}-${level}`
|
||
|
||
info.innerHTML = `
|
||
<div class="color-name">${level}</div>
|
||
<div class="color-code" data-hex="${hex}">${hex}</div>
|
||
`
|
||
|
||
// 点击卡片复制色值
|
||
card.addEventListener('click', () => {
|
||
const withHash = document.getElementById('withHash').checked
|
||
const copyText = withHash ? hex : hex.replace(/^#/, '')
|
||
copyToClipboard(copyText)
|
||
})
|
||
|
||
card.appendChild(swatch)
|
||
card.appendChild(info)
|
||
colorRow.appendChild(card)
|
||
})
|
||
|
||
resultSection.appendChild(colorRow)
|
||
}
|
||
|
||
/**
|
||
* 生成 CSS 变量代码
|
||
*/
|
||
function generateCssCode(results, varPrefix) {
|
||
const cssCode = document.getElementById('cssCode')
|
||
const baseVar = `--el-color-${varPrefix}`
|
||
|
||
let css = `/* 基于 ${results['主色']} 的 Element 色阶配置 */\n`
|
||
css += `${baseVar}: ${results['主色']} !important;\n`
|
||
css += `${baseVar}-dark-2: ${results['dark-2']} !important;\n`
|
||
css += `${baseVar}-light-3: ${results['light-3']} !important;\n`
|
||
css += `${baseVar}-light-5: ${results['light-5']} !important;\n`
|
||
css += `${baseVar}-light-7: ${results['light-7']} !important;\n`
|
||
css += `${baseVar}-light-8: ${results['light-8']} !important;\n`
|
||
css += `${baseVar}-light-9: ${results['light-9']} !important;`
|
||
|
||
// 移除原有按钮,重新添加(避免重复)
|
||
cssCode.innerHTML = ''
|
||
const copyBtn = document.createElement('button')
|
||
copyBtn.className = 'copy-btn'
|
||
copyBtn.textContent = '复制代码'
|
||
copyBtn.onclick = copyAllCss
|
||
cssCode.appendChild(copyBtn)
|
||
cssCode.appendChild(document.createTextNode(css))
|
||
}
|
||
|
||
/**
|
||
* 一键复制所有 CSS 变量
|
||
*/
|
||
function copyAllCss() {
|
||
const cssCode = document.getElementById('cssCode')
|
||
// 排除按钮,只复制文本内容
|
||
const text = cssCode.textContent.replace('复制代码', '').trim()
|
||
copyToClipboard(text)
|
||
}
|
||
|
||
// 页面加载时默认计算一次
|
||
window.onload = calculateColorLevels
|
||
</script>
|
||
</body>
|
||
</html>
|