4.1 Sciter CSS 扩展概述
4.1.1 CSS 扩展特性
Sciter 在标准 CSS 基础上提供了丰富的扩展特性:
graph TD
A[Sciter CSS] --> B[标准 CSS3]
A --> C[布局扩展]
A --> D[动画扩展]
A --> E[状态选择器]
A --> F[函数扩展]
B --> G[Flexbox]
B --> H[Grid]
B --> I[Transform]
C --> J[Flow 布局]
C --> K[Stack 布局]
C --> L[Table 布局]
D --> M[过渡动画]
D --> N[关键帧动画]
D --> O[物理动画]
E --> P[:hover]
E --> Q[:active]
E --> R[:focus]
E --> S[:disabled]
F --> T[calc()]
F --> U[var()]
F --> V[color()]
4.1.2 样式应用方式
// 1. 内联样式
const element = document.querySelector(".my-element");
element.style.color = "red";
element.style.backgroundColor = "#f0f0f0";
element.style.transform = "translateX(100px)";
// 2. CSS 类操作
element.classList.add("active", "highlighted");
element.classList.remove("inactive");
element.classList.toggle("visible");
// 3. CSS 自定义属性
element.style.setProperty("--primary-color", "#007bff");
element.style.setProperty("--font-size", "16px");
// 4. 动态样式表
const styleSheet = document.createElement("style");
styleSheet.textContent = `
.dynamic-class {
color: var(--primary-color);
font-size: var(--font-size);
transition: all 0.3s ease;
}
`;
document.head.appendChild(styleSheet);
// 5. Sciter 特有的样式设置
element.style.behavior = "button"; // 设置行为
element.style.flow = "horizontal"; // 设置流布局
element.style.aspectRatio = "16/9"; // 设置宽高比
4.1.3 CSS 变量和主题系统
/* 主题变量定义 */
:root {
/* 颜色系统 */
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--warning-color: #ffc107;
--info-color: #17a2b8;
/* 字体系统 */
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto;
--font-size-base: 14px;
--font-size-lg: 18px;
--font-size-sm: 12px;
--line-height-base: 1.5;
/* 间距系统 */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
/* 阴影系统 */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
--shadow-md: 0 4px 6px rgba(0,0,0,0.16);
--shadow-lg: 0 10px 25px rgba(0,0,0,0.19);
/* 边框系统 */
--border-radius-sm: 2px;
--border-radius-md: 4px;
--border-radius-lg: 8px;
--border-width: 1px;
--border-color: #dee2e6;
}
/* 深色主题 */
[data-theme="dark"] {
--primary-color: #0d6efd;
--secondary-color: #6c757d;
--bg-color: #212529;
--text-color: #ffffff;
--border-color: #495057;
}
/* 高对比度主题 */
[data-theme="high-contrast"] {
--primary-color: #000000;
--secondary-color: #ffffff;
--bg-color: #ffffff;
--text-color: #000000;
--border-color: #000000;
}
4.2 动态样式管理
4.2.1 样式管理器
class StyleManager {
constructor() {
this.styleSheets = new Map();
this.cssVariables = new Map();
this.themes = new Map();
this.currentTheme = "default";
this.init();
}
init() {
this.setupDefaultTheme();
this.observeSystemTheme();
}
// 创建样式表
createStyleSheet(id, css) {
if (this.styleSheets.has(id)) {
this.updateStyleSheet(id, css);
return;
}
const style = document.createElement("style");
style.id = id;
style.textContent = css;
document.head.appendChild(style);
this.styleSheets.set(id, style);
}
// 更新样式表
updateStyleSheet(id, css) {
const style = this.styleSheets.get(id);
if (style) {
style.textContent = css;
}
}
// 删除样式表
removeStyleSheet(id) {
const style = this.styleSheets.get(id);
if (style) {
style.remove();
this.styleSheets.delete(id);
}
}
// 设置 CSS 变量
setCSSVariable(name, value, element = document.documentElement) {
element.style.setProperty(`--${name}`, value);
this.cssVariables.set(name, value);
}
// 获取 CSS 变量
getCSSVariable(name, element = document.documentElement) {
return getComputedStyle(element).getPropertyValue(`--${name}`);
}
// 批量设置 CSS 变量
setCSSVariables(variables, element = document.documentElement) {
Object.entries(variables).forEach(([name, value]) => {
this.setCSSVariable(name, value, element);
});
}
// 注册主题
registerTheme(name, variables) {
this.themes.set(name, variables);
}
// 应用主题
applyTheme(name) {
const theme = this.themes.get(name);
if (!theme) {
console.warn(`主题 "${name}" 不存在`);
return;
}
this.setCSSVariables(theme);
this.currentTheme = name;
document.documentElement.dataset.theme = name;
// 触发主题变化事件
document.dispatchEvent(new CustomEvent("theme-changed", {
detail: { theme: name, variables: theme }
}));
}
// 获取当前主题
getCurrentTheme() {
return this.currentTheme;
}
// 监听系统主题变化
observeSystemTheme() {
if (window.matchMedia) {
const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
darkModeQuery.addListener((e) => {
if (this.currentTheme === "auto") {
this.applyTheme(e.matches ? "dark" : "light");
}
});
}
}
// 设置默认主题
setupDefaultTheme() {
this.registerTheme("light", {
"primary-color": "#007bff",
"bg-color": "#ffffff",
"text-color": "#212529",
"border-color": "#dee2e6"
});
this.registerTheme("dark", {
"primary-color": "#0d6efd",
"bg-color": "#212529",
"text-color": "#ffffff",
"border-color": "#495057"
});
this.applyTheme("light");
}
// 生成响应式样式
generateResponsiveCSS(breakpoints, styles) {
let css = "";
Object.entries(styles).forEach(([selector, rules]) => {
// 基础样式
if (rules.base) {
css += `${selector} { ${this.rulesToCSS(rules.base)} }\n`;
}
// 响应式样式
Object.entries(breakpoints).forEach(([name, width]) => {
if (rules[name]) {
css += `@media (min-width: ${width}) {\n`;
css += ` ${selector} { ${this.rulesToCSS(rules[name])} }\n`;
css += `}\n`;
}
});
});
return css;
}
// 将规则对象转换为 CSS 字符串
rulesToCSS(rules) {
return Object.entries(rules)
.map(([prop, value]) => `${this.camelToKebab(prop)}: ${value}`)
.join("; ");
}
// 驼峰转短横线
camelToKebab(str) {
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
}
}
// 使用示例
const styleManager = new StyleManager();
// 注册自定义主题
styleManager.registerTheme("custom", {
"primary-color": "#ff6b6b",
"secondary-color": "#4ecdc4",
"bg-color": "#f8f9fa",
"text-color": "#343a40"
});
// 应用主题
styleManager.applyTheme("custom");
// 动态创建响应式样式
const breakpoints = {
sm: "576px",
md: "768px",
lg: "992px",
xl: "1200px"
};
const responsiveStyles = {
".container": {
base: {
padding: "var(--spacing-md)",
margin: "0 auto"
},
sm: {
maxWidth: "540px"
},
md: {
maxWidth: "720px"
},
lg: {
maxWidth: "960px"
},
xl: {
maxWidth: "1140px"
}
}
};
const css = styleManager.generateResponsiveCSS(breakpoints, responsiveStyles);
styleManager.createStyleSheet("responsive", css);
4.2.2 动画系统
class AnimationManager {
constructor() {
this.animations = new Map();
this.easingFunctions = new Map();
this.setupEasingFunctions();
}
setupEasingFunctions() {
this.easingFunctions.set("linear", t => t);
this.easingFunctions.set("easeInQuad", t => t * t);
this.easingFunctions.set("easeOutQuad", t => t * (2 - t));
this.easingFunctions.set("easeInOutQuad", t =>
t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
);
this.easingFunctions.set("easeInCubic", t => t * t * t);
this.easingFunctions.set("easeOutCubic", t => (--t) * t * t + 1);
this.easingFunctions.set("easeInOutCubic", t =>
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
);
}
// 创建动画
animate(element, properties, options = {}) {
const config = {
duration: options.duration || 300,
easing: options.easing || "easeOutQuad",
delay: options.delay || 0,
onComplete: options.onComplete || (() => {}),
onUpdate: options.onUpdate || (() => {})
};
const animationId = `anim_${Date.now()}_${Math.random()}`;
// 获取初始值
const startValues = {};
const endValues = {};
Object.keys(properties).forEach(prop => {
startValues[prop] = this.getCurrentValue(element, prop);
endValues[prop] = properties[prop];
});
const animation = {
element,
startValues,
endValues,
config,
startTime: null,
cancelled: false
};
this.animations.set(animationId, animation);
// 延迟执行
setTimeout(() => {
if (!animation.cancelled) {
this.startAnimation(animationId);
}
}, config.delay);
return {
cancel: () => {
animation.cancelled = true;
this.animations.delete(animationId);
}
};
}
startAnimation(animationId) {
const animation = this.animations.get(animationId);
if (!animation) return;
const animate = (currentTime) => {
if (animation.cancelled) return;
if (!animation.startTime) {
animation.startTime = currentTime;
}
const elapsed = currentTime - animation.startTime;
const progress = Math.min(elapsed / animation.config.duration, 1);
// 应用缓动函数
const easingFunction = this.easingFunctions.get(animation.config.easing);
const easedProgress = easingFunction ? easingFunction(progress) : progress;
// 更新属性值
Object.keys(animation.endValues).forEach(prop => {
const startValue = animation.startValues[prop];
const endValue = animation.endValues[prop];
const currentValue = this.interpolateValue(startValue, endValue, easedProgress);
this.setElementProperty(animation.element, prop, currentValue);
});
// 调用更新回调
animation.config.onUpdate(easedProgress);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画完成
animation.config.onComplete();
this.animations.delete(animationId);
}
};
requestAnimationFrame(animate);
}
getCurrentValue(element, property) {
const computedStyle = getComputedStyle(element);
const value = computedStyle.getPropertyValue(property);
// 解析数值
if (property === "opacity") {
return parseFloat(value) || 0;
}
if (property.includes("transform")) {
return this.parseTransform(value);
}
// 解析像素值
const numericValue = parseFloat(value);
return isNaN(numericValue) ? 0 : numericValue;
}
setElementProperty(element, property, value) {
if (property === "opacity") {
element.style.opacity = value;
} else if (property.startsWith("translate")) {
const currentTransform = element.style.transform || "";
const newTransform = this.updateTransform(currentTransform, property, value);
element.style.transform = newTransform;
} else {
element.style[property] = typeof value === "number" ? `${value}px` : value;
}
}
interpolateValue(start, end, progress) {
if (typeof start === "number" && typeof end === "number") {
return start + (end - start) * progress;
}
// 颜色插值
if (typeof start === "string" && typeof end === "string") {
if (start.startsWith("#") && end.startsWith("#")) {
return this.interpolateColor(start, end, progress);
}
}
return progress < 0.5 ? start : end;
}
interpolateColor(startColor, endColor, progress) {
const start = this.hexToRgb(startColor);
const end = this.hexToRgb(endColor);
const r = Math.round(start.r + (end.r - start.r) * progress);
const g = Math.round(start.g + (end.g - start.g) * progress);
const b = Math.round(start.b + (end.b - start.b) * progress);
return `rgb(${r}, ${g}, ${b})`;
}
hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
parseTransform(transform) {
// 简化的 transform 解析
const translateMatch = transform.match(/translateX?\(([^)]+)\)/);
if (translateMatch) {
return parseFloat(translateMatch[1]) || 0;
}
return 0;
}
updateTransform(currentTransform, property, value) {
// 简化的 transform 更新
const transforms = currentTransform.split(" ").filter(t => t);
const newTransform = `${property}(${value}px)`;
// 替换或添加新的 transform
const existingIndex = transforms.findIndex(t => t.startsWith(property));
if (existingIndex >= 0) {
transforms[existingIndex] = newTransform;
} else {
transforms.push(newTransform);
}
return transforms.join(" ");
}
// 预定义动画
fadeIn(element, duration = 300) {
element.style.opacity = "0";
return this.animate(element, { opacity: 1 }, { duration });
}
fadeOut(element, duration = 300) {
return this.animate(element, { opacity: 0 }, { duration });
}
slideIn(element, direction = "left", duration = 300) {
const distance = direction === "left" || direction === "right" ?
element.offsetWidth : element.offsetHeight;
const startValue = direction === "left" ? -distance :
direction === "right" ? distance :
direction === "up" ? -distance : distance;
const property = direction === "left" || direction === "right" ?
"translateX" : "translateY";
this.setElementProperty(element, property, startValue);
return this.animate(element, { [property]: 0 }, { duration });
}
slideOut(element, direction = "left", duration = 300) {
const distance = direction === "left" || direction === "right" ?
element.offsetWidth : element.offsetHeight;
const endValue = direction === "left" ? -distance :
direction === "right" ? distance :
direction === "up" ? -distance : distance;
const property = direction === "left" || direction === "right" ?
"translateX" : "translateY";
return this.animate(element, { [property]: endValue }, { duration });
}
}
// 使用示例
const animationManager = new AnimationManager();
// 基础动画
const element = document.querySelector(".animate-me");
animationManager.animate(element, {
translateX: 100,
opacity: 0.5
}, {
duration: 500,
easing: "easeInOutQuad",
onComplete: () => console.log("动画完成")
});
// 预定义动画
animationManager.fadeIn(element);
animationManager.slideIn(element, "right", 400);
4.3 布局系统
4.3.1 Flexbox 布局
/* Flexbox 容器 */
.flex-container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
flex-wrap: nowrap; /* nowrap | wrap | wrap-reverse */
justify-content: flex-start; /* flex-start | flex-end | center | space-between | space-around | space-evenly */
align-items: stretch; /* stretch | flex-start | flex-end | center | baseline */
align-content: stretch; /* stretch | flex-start | flex-end | center | space-between | space-around */
gap: var(--spacing-md);
}
/* Flexbox 项目 */
.flex-item {
flex: 1; /* flex-grow flex-shrink flex-basis */
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
align-self: auto; /* auto | flex-start | flex-end | center | baseline | stretch */
order: 0;
}
/* 常用 Flexbox 工具类 */
.d-flex { display: flex; }
.d-inline-flex { display: inline-flex; }
.flex-row { flex-direction: row; }
.flex-column { flex-direction: column; }
.flex-row-reverse { flex-direction: row-reverse; }
.flex-column-reverse { flex-direction: column-reverse; }
.flex-wrap { flex-wrap: wrap; }
.flex-nowrap { flex-wrap: nowrap; }
.flex-wrap-reverse { flex-wrap: wrap-reverse; }
.justify-content-start { justify-content: flex-start; }
.justify-content-end { justify-content: flex-end; }
.justify-content-center { justify-content: center; }
.justify-content-between { justify-content: space-between; }
.justify-content-around { justify-content: space-around; }
.justify-content-evenly { justify-content: space-evenly; }
.align-items-start { align-items: flex-start; }
.align-items-end { align-items: flex-end; }
.align-items-center { align-items: center; }
.align-items-baseline { align-items: baseline; }
.align-items-stretch { align-items: stretch; }
.flex-fill { flex: 1 1 auto; }
.flex-grow-0 { flex-grow: 0; }
.flex-grow-1 { flex-grow: 1; }
.flex-shrink-0 { flex-shrink: 0; }
.flex-shrink-1 { flex-shrink: 1; }
4.3.2 Grid 布局
/* Grid 容器 */
.grid-container {
display: grid;
grid-template-columns: repeat(12, 1fr); /* 12列网格 */
grid-template-rows: auto;
grid-gap: var(--spacing-md);
grid-auto-flow: row; /* row | column | row dense | column dense */
}
/* Grid 项目 */
.grid-item {
grid-column: span 1; /* 占用列数 */
grid-row: span 1; /* 占用行数 */
grid-area: auto; /* 网格区域 */
}
/* 命名网格线 */
.layout-grid {
display: grid;
grid-template-columns:
[sidebar-start] 250px
[sidebar-end main-start] 1fr
[main-end];
grid-template-rows:
[header-start] 60px
[header-end content-start] 1fr
[content-end footer-start] 40px
[footer-end];
grid-template-areas:
"sidebar header"
"sidebar main"
"sidebar footer";
}
.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* 响应式网格 */
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-md);
}
/* Grid 工具类 */
.d-grid { display: grid; }
.d-inline-grid { display: inline-grid; }
.grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid-cols-6 { grid-template-columns: repeat(6, 1fr); }
.grid-cols-12 { grid-template-columns: repeat(12, 1fr); }
.col-span-1 { grid-column: span 1; }
.col-span-2 { grid-column: span 2; }
.col-span-3 { grid-column: span 3; }
.col-span-4 { grid-column: span 4; }
.col-span-6 { grid-column: span 6; }
.col-span-12 { grid-column: span 12; }
.row-span-1 { grid-row: span 1; }
.row-span-2 { grid-row: span 2; }
.row-span-3 { grid-row: span 3; }
4.3.3 Sciter 特有布局
/* Flow 布局 - Sciter 特有 */
.flow-container {
flow: horizontal; /* horizontal | vertical | horizontal-wrap | vertical-wrap */
width: 100%;
height: 100%;
}
.flow-item {
width: max-content;
height: max-content;
}
/* Stack 布局 - Sciter 特有 */
.stack-container {
flow: stack;
width: 100%;
height: 100%;
}
.stack-item {
position: absolute;
width: 100%;
height: 100%;
}
/* Table 布局 - Sciter 增强 */
.table-container {
flow: table;
width: 100%;
table-layout: fixed;
}
.table-row {
flow: table-row;
}
.table-cell {
flow: table-cell;
vertical-align: middle;
padding: var(--spacing-sm);
}
/* 自适应布局 */
.adaptive-layout {
flow: horizontal;
width: 100%;
}
.adaptive-layout > * {
width: max-content;
min-width: 0;
}
.adaptive-layout > .fill {
width: *; /* Sciter 特有:占用剩余空间 */
}
4.3.4 响应式布局管理器
class ResponsiveLayoutManager {
constructor() {
this.breakpoints = {
xs: 0,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
xxl: 1400
};
this.currentBreakpoint = this.getCurrentBreakpoint();
this.observers = new Set();
this.setupMediaQueries();
}
setupMediaQueries() {
Object.entries(this.breakpoints).forEach(([name, width]) => {
if (width > 0) {
const mediaQuery = window.matchMedia(`(min-width: ${width}px)`);
mediaQuery.addListener(() => {
const newBreakpoint = this.getCurrentBreakpoint();
if (newBreakpoint !== this.currentBreakpoint) {
this.currentBreakpoint = newBreakpoint;
this.notifyObservers(newBreakpoint);
}
});
}
});
}
getCurrentBreakpoint() {
const width = window.innerWidth;
let currentBreakpoint = "xs";
Object.entries(this.breakpoints).forEach(([name, minWidth]) => {
if (width >= minWidth) {
currentBreakpoint = name;
}
});
return currentBreakpoint;
}
onBreakpointChange(callback) {
this.observers.add(callback);
return () => this.observers.delete(callback);
}
notifyObservers(breakpoint) {
this.observers.forEach(callback => {
try {
callback(breakpoint, this.breakpoints[breakpoint]);
} catch (error) {
console.error("响应式回调错误:", error);
}
});
}
// 根据断点应用不同的布局
applyResponsiveLayout(element, layouts) {
const layout = layouts[this.currentBreakpoint] || layouts.xs || {};
Object.entries(layout).forEach(([property, value]) => {
if (property === "class") {
// 移除所有响应式类
Object.values(layouts).forEach(l => {
if (l.class) {
element.classList.remove(l.class);
}
});
// 添加当前断点的类
element.classList.add(value);
} else {
element.style[property] = value;
}
});
}
// 创建响应式组件
createResponsiveComponent(element, config) {
const applyLayout = () => {
this.applyResponsiveLayout(element, config.layouts || {});
if (config.onBreakpointChange) {
config.onBreakpointChange(this.currentBreakpoint, element);
}
};
// 初始应用
applyLayout();
// 监听变化
const unsubscribe = this.onBreakpointChange(applyLayout);
return {
destroy: unsubscribe,
getCurrentBreakpoint: () => this.currentBreakpoint,
updateConfig: (newConfig) => {
Object.assign(config, newConfig);
applyLayout();
}
};
}
}
// 使用示例
const layoutManager = new ResponsiveLayoutManager();
// 监听断点变化
layoutManager.onBreakpointChange((breakpoint, width) => {
console.log(`断点变化: ${breakpoint} (${width}px)`);
document.body.dataset.breakpoint = breakpoint;
});
// 创建响应式导航
const nav = document.querySelector(".navbar");
const responsiveNav = layoutManager.createResponsiveComponent(nav, {
layouts: {
xs: {
class: "navbar-mobile",
flexDirection: "column"
},
md: {
class: "navbar-desktop",
flexDirection: "row"
}
},
onBreakpointChange: (breakpoint, element) => {
if (breakpoint === "xs") {
element.querySelector(".navbar-toggle").style.display = "block";
} else {
element.querySelector(".navbar-toggle").style.display = "none";
}
}
});
// 创建响应式网格
const grid = document.querySelector(".responsive-grid");
const responsiveGrid = layoutManager.createResponsiveComponent(grid, {
layouts: {
xs: {
gridTemplateColumns: "1fr"
},
sm: {
gridTemplateColumns: "repeat(2, 1fr)"
},
md: {
gridTemplateColumns: "repeat(3, 1fr)"
},
lg: {
gridTemplateColumns: "repeat(4, 1fr)"
}
}
});
4.4 实践练习
4.4.1 主题切换器
class ThemeSwitcher {
constructor(container) {
this.container = container;
this.styleManager = new StyleManager();
this.currentTheme = localStorage.getItem("theme") || "auto";
this.init();
}
init() {
this.setupThemes();
this.createUI();
this.setupEvents();
this.applyTheme(this.currentTheme);
}
setupThemes() {
// 注册主题
this.styleManager.registerTheme("light", {
"bg-color": "#ffffff",
"text-color": "#212529",
"primary-color": "#007bff",
"secondary-color": "#6c757d",
"border-color": "#dee2e6",
"shadow-color": "rgba(0,0,0,0.1)"
});
this.styleManager.registerTheme("dark", {
"bg-color": "#212529",
"text-color": "#ffffff",
"primary-color": "#0d6efd",
"secondary-color": "#6c757d",
"border-color": "#495057",
"shadow-color": "rgba(255,255,255,0.1)"
});
this.styleManager.registerTheme("sepia", {
"bg-color": "#f4f3e8",
"text-color": "#5c4b37",
"primary-color": "#8b4513",
"secondary-color": "#a0522d",
"border-color": "#d2b48c",
"shadow-color": "rgba(92,75,55,0.1)"
});
this.styleManager.registerTheme("high-contrast", {
"bg-color": "#000000",
"text-color": "#ffffff",
"primary-color": "#ffff00",
"secondary-color": "#00ffff",
"border-color": "#ffffff",
"shadow-color": "rgba(255,255,255,0.3)"
});
}
createUI() {
this.container.innerHTML = `
<div class="theme-switcher">
<label class="theme-switcher-label">主题设置</label>
<div class="theme-options">
<button class="theme-option" data-theme="auto">
<span class="theme-icon">🌓</span>
<span class="theme-name">自动</span>
</button>
<button class="theme-option" data-theme="light">
<span class="theme-icon">☀️</span>
<span class="theme-name">浅色</span>
</button>
<button class="theme-option" data-theme="dark">
<span class="theme-icon">🌙</span>
<span class="theme-name">深色</span>
</button>
<button class="theme-option" data-theme="sepia">
<span class="theme-icon">📜</span>
<span class="theme-name">护眼</span>
</button>
<button class="theme-option" data-theme="high-contrast">
<span class="theme-icon">🔆</span>
<span class="theme-name">高对比</span>
</button>
</div>
<div class="theme-preview">
<div class="preview-card">
<div class="preview-header">预览</div>
<div class="preview-content">
<p>这是主题预览文本</p>
<button class="preview-button">按钮</button>
</div>
</div>
</div>
</div>
`;
// 添加样式
this.styleManager.createStyleSheet("theme-switcher", `
.theme-switcher {
padding: var(--spacing-lg);
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-md);
}
.theme-switcher-label {
display: block;
font-weight: bold;
margin-bottom: var(--spacing-md);
color: var(--text-color);
}
.theme-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
}
.theme-option {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--spacing-md);
border: 2px solid var(--border-color);
border-radius: var(--border-radius-md);
background: var(--bg-color);
color: var(--text-color);
cursor: pointer;
transition: all 0.2s ease;
}
.theme-option:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
}
.theme-option.active {
border-color: var(--primary-color);
background: var(--primary-color);
color: white;
}
.theme-icon {
font-size: 24px;
margin-bottom: var(--spacing-xs);
}
.theme-name {
font-size: 12px;
font-weight: 500;
}
.theme-preview {
border: 1px solid var(--border-color);
border-radius: var(--border-radius-md);
overflow: hidden;
}
.preview-card {
background: var(--bg-color);
color: var(--text-color);
}
.preview-header {
padding: var(--spacing-md);
background: var(--primary-color);
color: white;
font-weight: bold;
}
.preview-content {
padding: var(--spacing-md);
}
.preview-button {
padding: var(--spacing-sm) var(--spacing-md);
background: var(--primary-color);
color: white;
border: none;
border-radius: var(--border-radius-sm);
cursor: pointer;
margin-top: var(--spacing-sm);
}
`);
}
setupEvents() {
this.container.on("click", ".theme-option", (event) => {
const theme = event.currentTarget.dataset.theme;
this.applyTheme(theme);
});
// 监听系统主题变化
if (window.matchMedia) {
const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
darkModeQuery.addListener(() => {
if (this.currentTheme === "auto") {
this.applyAutoTheme();
}
});
}
}
applyTheme(theme) {
this.currentTheme = theme;
localStorage.setItem("theme", theme);
// 更新按钮状态
this.container.querySelectorAll(".theme-option").forEach(btn => {
btn.classList.toggle("active", btn.dataset.theme === theme);
});
if (theme === "auto") {
this.applyAutoTheme();
} else {
this.styleManager.applyTheme(theme);
}
// 添加过渡动画
document.body.style.transition = "background-color 0.3s ease, color 0.3s ease";
setTimeout(() => {
document.body.style.transition = "";
}, 300);
}
applyAutoTheme() {
const prefersDark = window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
this.styleManager.applyTheme(prefersDark ? "dark" : "light");
}
}
// 使用示例
document.ready = function() {
const themeSwitcher = new ThemeSwitcher(document.querySelector(".theme-switcher-container"));
};
4.5 本章小结
4.5.1 核心概念
- CSS 扩展:Sciter 在标准 CSS 基础上的增强特性
- 动态样式:JavaScript 控制的样式管理和主题系统
- 布局系统:Flexbox、Grid 和 Sciter 特有的 Flow 布局
- 响应式设计:适配不同屏幕尺寸的布局策略
4.5.2 技术要点
- 性能优化:合理使用 CSS 变量和样式缓存
- 主题管理:系统化的主题切换和持久化
- 动画优化:使用 requestAnimationFrame 和硬件加速
- 响应式布局:移动优先的设计理念
4.5.3 最佳实践
- 使用 CSS 变量构建可维护的主题系统
- 优先使用 Flexbox 和 Grid 进行布局
- 实现平滑的主题切换动画
- 提供完整的响应式支持
4.5.4 下一章预告
下一章将学习 组件开发与模块化,包括: - 组件架构设计 - 生命周期管理 - 组件通信机制 - 模块化开发模式
通过本章的学习,你已经掌握了完整的样式和布局技能,能够创建美观且响应式的用户界面。