Files
MetonaToast/src/styles.js
T
thzxx b4ffaabedd chore: v2.0.1 - 精简dead code + 版本升级
- utils.js: 950行→180行,移除30+未使用函数
- styles.js: 1800行→650行,移除未使用组件样式和重复规则
- animations.js: 850行→120行,移除Web Animations API dead code
- 全项目版本号升级到 2.0.1
- 测试同步清理,134/134通过
2026-07-23 13:18:24 +08:00

617 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* MetonaToast Styles - 样式管理(精简版 v2.0.1
* @module styles
* @description 样式注入与主题管理,移除未使用的组件样式
*/
import { THEMES } from './constants.js';
// 样式缓存
let styleElement = null;
/**
* 生成CSS样式
*/
const generateCSS = (theme) => {
return `
/* MetonaToast 基础样式 */
.met-container {
pointer-events: none;
display: flex;
flex-direction: column;
gap: 12px;
box-sizing: border-box;
padding: 24px;
max-width: 100vw;
position: fixed;
z-index: 9999;
}
/* 位置样式 */
.met-container.top-left { top: 0; left: 0; align-items: flex-start; }
.met-container.top-center { top: 0; left: 0; right: 0; align-items: center; }
.met-container.top-right { top: 0; right: 0; align-items: flex-end; }
.met-container.bottom-left { bottom: 0; left: 0; align-items: flex-start; }
.met-container.bottom-center{ bottom: 0; left: 0; right: 0; align-items: center; }
.met-container.bottom-right { bottom: 0; right: 0; align-items: flex-end; }
/* Toast基础样式 */
.met-toast {
position: relative;
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
border-radius: 12px;
box-sizing: border-box;
backdrop-filter: blur(14px) saturate(180%);
-webkit-backdrop-filter: blur(14px) saturate(180%);
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
user-select: none;
-webkit-user-select: none;
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.25s ease,
opacity 0.25s ease;
will-change: transform, opacity;
flex-shrink: 0;
pointer-events: auto;
min-width: 240px;
max-width: calc(100vw - 48px);
width: 360px;
border: 1px solid var(--met-border, rgba(0,0,0,0.06));
background: var(--met-bg, rgba(255,255,255,0.96));
color: var(--met-text, #1f2937);
box-shadow: var(--met-shadow, 0 10px 36px -10px rgba(0,0,0,0.18), 0 4px 14px -4px rgba(0,0,0,0.08));
transform: translateZ(0);
-webkit-transform: translateZ(0);
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
perspective: 1000;
}
/* 悬停效果 */
.met-toast:hover {
box-shadow: var(--met-hover-shadow, 0 14px 48px -10px rgba(0,0,0,0.22), 0 6px 18px -4px rgba(0,0,0,0.10)) !important;
}
/* 可点击状态 */
.met-toast.met-clickable {
cursor: pointer;
}
/* 图标样式 */
.met-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-top: 1px;
}
/* 内容样式 */
.met-content {
flex: 1;
min-width: 0;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* 标题样式 */
.met-title {
font-weight: 600;
font-size: 14px;
letter-spacing: 0.1px;
}
/* 消息样式 */
.met-message {
font-size: 13px;
opacity: 0.85;
margin-top: 2px;
}
/* 关闭按钮样式 */
.met-close {
flex-shrink: 0;
background: transparent;
border: 0;
padding: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: background 0.2s, color 0.2s;
line-height: 0;
color: inherit;
opacity: 0.5;
}
.met-close:hover {
opacity: 1;
background: var(--met-close-hover-bg, rgba(0,0,0,0.06));
}
/* 进度条样式 - 水平 */
.met-progress {
position: absolute;
left: 0; right: 0; bottom: 0;
height: 3px;
overflow: hidden;
background: var(--met-progress-bg, rgba(0,0,0,0.06));
border-radius: 0 0 12px 12px;
}
/* 进度条样式 - 垂直 */
.met-progress-v {
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
overflow: hidden;
background: var(--met-progress-bg, rgba(0,0,0,0.06));
border-radius: 12px 0 0 12px;
}
/* 进度条指示器 - 水平 */
.met-bar {
position: absolute;
left: 0; top: 0;
height: 100%; width: 100%;
transform-origin: left;
transform: scaleX(1);
}
/* 进度条指示器 - 垂直 */
.met-bar-v {
position: absolute;
left: 0; bottom: 0;
width: 100%; height: 100%;
transform-origin: bottom;
transform: scaleY(1);
}
/* 侧边指示器 */
.met-side {
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
border-radius: 12px 0 0 12px;
}
/* 加载动画 */
.met-spin {
animation: met-rot 1s linear infinite;
transform-origin: 50% 50%;
}
/* 离开状态 */
.met-toast.met-leaving {
pointer-events: none;
z-index: 1;
}
@keyframes met-rot {
to { transform: rotate(360deg); }
}
/* === 进入动画:初始隐藏态 === */
.met-anim-slide.met-toast,
.met-anim-fade.met-toast,
.met-anim-scale.met-toast,
.met-anim-bounce.met-toast,
.met-anim-flip.met-toast,
.met-anim-rotate.met-toast,
.met-anim-zoom.met-toast,
.met-anim-slideUp.met-toast,
.met-anim-slideDown.met-toast,
.met-anim-slideLeft.met-toast,
.met-anim-slideRight.met-toast {
opacity: 0;
}
/* === 进入动画:@keyframes 定义 === */
@keyframes met-slide-in {
0% { transform: translateX(80px); opacity: 0; }
65% { transform: translateX(-6px); opacity: 1; }
100% { transform: translateX(0); opacity: 1; }
}
@keyframes met-fade-in {
0% { opacity: 0; filter: blur(3px); }
100% { opacity: 1; filter: blur(0); }
}
@keyframes met-scale-in {
0% { transform: scale(0.55); opacity: 0; }
65% { transform: scale(1.07); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes met-bounce-in {
0% { transform: translateY(-80px); opacity: 0; animation-timing-function: ease-in; }
45% { transform: translateY(14px); opacity: 1; animation-timing-function: ease-out; }
65% { transform: translateY(-12px); animation-timing-function: ease-in; }
82% { transform: translateY(4px); animation-timing-function: ease-out; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes met-flip-in {
0% { transform: perspective(500px) rotateX(-90deg); opacity: 0; }
60% { transform: perspective(500px) rotateX(8deg); opacity: 1; }
85% { transform: perspective(500px) rotateX(-2deg); }
100% { transform: perspective(500px) rotateX(0); opacity: 1; }
}
@keyframes met-rotate-in {
0% { transform: rotate(-25deg) scale(0.6); opacity: 0; }
60% { transform: rotate(4deg) scale(1.05); opacity: 1; }
85% { transform: rotate(-1deg) scale(0.98); }
100% { transform: rotate(0) scale(1); opacity: 1; }
}
@keyframes met-zoom-in {
0% { transform: scale(0.1); opacity: 0; }
50% { transform: scale(1.12); opacity: 1; }
75% { transform: scale(0.94); }
100% { transform: scale(1); opacity: 1; }
}
@keyframes met-slideUp-in {
0% { transform: translateY(60px); opacity: 0; }
70% { transform: translateY(-6px); opacity: 1; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes met-slideDown-in {
0% { transform: translateY(-60px); opacity: 0; }
70% { transform: translateY(6px); opacity: 1; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes met-slideLeft-in {
0% { transform: translateX(-90px); opacity: 0; }
65% { transform: translateX(6px); opacity: 1; }
100% { transform: translateX(0); opacity: 1; }
}
@keyframes met-slideRight-in {
0% { transform: translateX(90px); opacity: 0; }
65% { transform: translateX(-6px); opacity: 1; }
100% { transform: translateX(0); opacity: 1; }
}
/* === 进入动画:绑定到 .met-show === */
.met-anim-slide.met-toast.met-show { animation: met-slide-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-fade.met-toast.met-show { animation: met-fade-in 0.50s ease forwards; }
.met-anim-scale.met-toast.met-show { animation: met-scale-in 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
.met-anim-bounce.met-toast.met-show { animation: met-bounce-in 0.65s ease forwards; }
.met-anim-flip.met-toast.met-show { animation: met-flip-in 0.50s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-rotate.met-toast.met-show { animation: met-rotate-in 0.50s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-zoom.met-toast.met-show { animation: met-zoom-in 0.50s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
.met-anim-slideUp.met-toast.met-show { animation: met-slideUp-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-slideDown.met-toast.met-show { animation: met-slideDown-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-slideLeft.met-toast.met-show { animation: met-slideLeft-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
.met-anim-slideRight.met-toast.met-show { animation: met-slideRight-in 0.40s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; }
/* 响应式设计 */
@media (max-width: 480px) {
.met-container { padding: 12px !important; }
.met-toast { width: 100% !important; min-width: 0 !important; }
}
@media (max-width: 768px) {
.met-toast { font-size: 13px; }
.met-title { font-size: 13px; }
.met-message { font-size: 12px; }
}
@media (min-width: 1200px) {
.met-toast { width: 400px; }
}
@media (min-width: 1920px) {
.met-toast { width: 440px; }
}
@media (max-width: 320px) {
.met-toast { width: 100%; min-width: 0; padding: 12px 14px; }
.met-container { padding: 8px; }
}
@media (orientation: landscape) and (max-height: 500px) {
.met-container { padding: 12px 24px; }
.met-toast { min-height: 44px; padding: 10px 14px; }
}
@media (orientation: portrait) and (max-width: 500px) {
.met-container { padding: 12px; }
.met-toast { width: 100%; min-width: 0; }
}
/* 无障碍支持 */
.met-toast[role="alert"] { }
.met-toast[role="status"] { }
@media (prefers-reduced-motion: reduce) {
.met-toast { transition: opacity 0.15s !important; }
.met-toast.met-show { animation: none !important; opacity: 1 !important; }
.met-spin { animation-duration: 2.5s !important; }
}
@media (forced-colors: active) {
.met-toast { border: 2px solid ButtonText; }
.met-close { border: 1px solid ButtonText; }
}
@media (prefers-contrast: high) {
.met-toast { border-width: 3px; border-style: solid; }
.met-close { border: 2px solid currentColor; border-radius: 4px; }
}
/* 焦点样式 */
.met-toast:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
.met-close:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* 触摸设备优化 */
@media (hover: none) and (pointer: coarse) {
.met-toast:hover { box-shadow: inherit; }
.met-toast { min-height: 48px; }
.met-close { min-width: 48px; min-height: 48px; }
}
@media (hover: none) and (pointer: coarse) {
.met-toast { min-height: 52px; padding: 16px 18px; }
.met-close { min-width: 52px; min-height: 52px; padding: 8px; }
.met-icon { width: 28px; height: 28px; }
}
/* 打印样式 */
@media print {
.met-container { display: none !important; }
.met-toast { display: none !important; }
}
/* 全屏/画中画模式 */
:fullscreen .met-container,
:picture-in-picture .met-container {
z-index: 2147483647;
}
/* 安全区域适配 */
@supports (padding: max(0px)) {
.met-container {
padding: max(24px, env(safe-area-inset-top))
max(24px, env(safe-area-inset-right))
max(24px, env(safe-area-inset-bottom))
max(24px, env(safe-area-inset-left));
}
}
/* 按下状态 */
.met-toast:active {
transform: scale(0.98);
}
/* 类型左侧边框 */
.met-toast.met-success { border-left: 4px solid #10b981; }
.met-toast.met-error { border-left: 4px solid #ef4444; }
.met-toast.met-warning { border-left: 4px solid #f59e0b; }
.met-toast.met-info { border-left: 4px solid #3b82f6; }
.met-toast.met-loading { border-left: 4px solid #6366f1; }
/* 进度条过渡 */
.met-bar, .met-bar-v {
transition: transform 0.1s linear;
}
/* 滚动条样式 */
.met-container::-webkit-scrollbar { width: 6px; height: 6px; }
.met-container::-webkit-scrollbar-track { background: transparent; }
.met-container::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.2); border-radius: 3px; }
.met-container::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.3); }
/* 暗色主题特定样式 */
.met-toast.met-theme-dark {
background: rgba(28, 32, 40, 0.94);
color: #e6e8eb;
border-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 10px 36px -10px rgba(0, 0, 0, 0.6), 0 4px 14px -4px rgba(0, 0, 0, 0.4);
}
.met-toast.met-theme-dark:hover {
box-shadow: 0 14px 48px -8px rgba(0, 0, 0, 0.6), 0 6px 18px -4px rgba(0, 0, 0, 0.4) !important;
}
.met-toast.met-theme-dark .met-close:hover {
background: rgba(255, 255, 255, 0.08);
}
.met-toast.met-theme-dark .met-progress,
.met-toast.met-theme-dark .met-progress-v {
background: rgba(255, 255, 255, 0.08);
}
/* 亮色主题特定样式 */
.met-toast.met-theme-light {
background: rgba(255, 255, 255, 0.96);
color: #1f2937;
border-color: rgba(0, 0, 0, 0.06);
box-shadow: 0 10px 36px -10px rgba(0, 0, 0, 0.18), 0 4px 14px -4px rgba(0, 0, 0, 0.08);
}
.met-toast.met-theme-light:hover {
box-shadow: 0 14px 48px -10px rgba(0, 0, 0, 0.22), 0 6px 18px -4px rgba(0, 0, 0, 0.10) !important;
}
.met-toast.met-theme-light .met-close:hover {
background: rgba(0, 0, 0, 0.06);
}
.met-toast.met-theme-light .met-progress,
.met-toast.met-theme-light .met-progress-v {
background: rgba(0, 0, 0, 0.06);
}
/* 自定义主题支持 */
.met-toast[data-theme] {
--met-bg: var(--met-theme-bg);
--met-text: var(--met-theme-text);
--met-border: var(--met-theme-border);
--met-shadow: var(--met-theme-shadow);
}
/* 暗色模式自动检测 */
@media (prefers-color-scheme: dark) {
.met-theme-auto .met-toast {
background: rgba(28, 32, 40, 0.94);
color: #e6e8eb;
border-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 10px 36px -10px rgba(0, 0, 0, 0.6), 0 4px 14px -4px rgba(0, 0, 0, 0.4);
}
.met-theme-auto .met-toast:hover {
box-shadow: 0 14px 48px -8px rgba(0, 0, 0, 0.6), 0 6px 18px -4px rgba(0, 0, 0, 0.4) !important;
}
.met-theme-auto .met-close:hover { background: rgba(255, 255, 255, 0.08); }
.met-theme-auto .met-progress,
.met-theme-auto .met-progress-v { background: rgba(255, 255, 255, 0.08); }
}
@media (prefers-color-scheme: light) {
.met-theme-auto .met-toast {
background: rgba(255, 255, 255, 0.96);
color: #1f2937;
border-color: rgba(0, 0, 0, 0.06);
box-shadow: 0 10px 36px -10px rgba(0, 0, 0, 0.18), 0 4px 14px -4px rgba(0, 0, 0, 0.08);
}
.met-theme-auto .met-toast:hover {
box-shadow: 0 14px 48px -10px rgba(0, 0, 0, 0.22), 0 6px 18px -4px rgba(0, 0, 0, 0.10) !important;
}
.met-theme-auto .met-close:hover { background: rgba(0, 0, 0, 0.06); }
.met-theme-auto .met-progress,
.met-theme-auto .met-progress-v { background: rgba(0, 0, 0, 0.06); }
}
`;
};
/**
* 注入样式
*/
export const injectStyles = (theme = THEMES.light) => {
if (typeof document === 'undefined') return;
if (styleElement && document.getElementById('metona-toast-styles')) {
return;
}
const css = generateCSS(theme);
styleElement = document.createElement('style');
styleElement.id = 'metona-toast-styles';
styleElement.textContent = css.replace(/\s+/g, ' ');
document.head.appendChild(styleElement);
};
/**
* 更新样式
*/
export const updateStyles = (theme) => {
if (!styleElement) {
injectStyles(theme);
return;
}
const css = generateCSS(theme);
styleElement.textContent = css.replace(/\s+/g, ' ');
};
/**
* 移除样式
*/
export const removeStyles = () => {
if (styleElement && styleElement.parentNode) {
styleElement.parentNode.removeChild(styleElement);
}
styleElement = null;
};
/**
* 生成主题CSS变量
*/
export const generateThemeVariables = (theme) => {
return `
:root {
--met-theme-bg: ${theme.bg};
--met-theme-text: ${theme.text};
--met-theme-border: ${theme.border};
--met-theme-shadow: ${theme.shadow};
--met-theme-hover-shadow: ${theme.hoverShadow};
--met-theme-progress-bg: ${theme.progressBg};
--met-theme-close-hover-bg: ${theme.closeHoverBg};
}
`;
};
/**
* 应用主题变量
*/
export const applyThemeVariables = (theme) => {
if (typeof document === 'undefined') return;
const css = generateThemeVariables(theme);
const customStyle = document.createElement('style');
customStyle.id = 'metona-toast-custom-styles';
customStyle.textContent = css;
document.head.appendChild(customStyle);
};
/**
* 清除主题变量
*/
export const clearThemeVariables = () => {
const customStyle = document.getElementById('metona-toast-custom-styles');
if (customStyle && customStyle.parentNode) {
customStyle.parentNode.removeChild(customStyle);
}
};
/**
* 获取系统主题
*/
export const getSystemTheme = () => {
if (typeof window === 'undefined') return 'light';
return window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};
/**
* 监听系统主题变化
*/
export const watchSystemTheme = (callback) => {
if (typeof window === 'undefined' || !window.matchMedia) {
return () => {};
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e) => {
callback(e.matches ? 'dark' : 'light');
};
mediaQuery.addEventListener('change', handler);
return () => {
mediaQuery.removeEventListener('change', handler);
};
};
/**
* 自动应用系统主题
*/
export const autoApplySystemTheme = () => {
const applyTheme = (theme) => {
updateStyles(THEMES[theme]);
applyThemeVariables(THEMES[theme]);
};
applyTheme(getSystemTheme());
return watchSystemTheme(applyTheme);
};