yyf_aka-web-components
Version:
A collection of modern Web Components including counter, data table and custom button components
402 lines (355 loc) • 12 kB
JavaScript
/**
* 计数器组件 - Web Component 示例
*
* 功能特性:
* - 自定义元素:使用 <counter-component> 标签
* - Shadow DOM:封装样式和结构
* - 属性绑定:支持初始值、步长、最小值、最大值
* - 事件系统:触发自定义事件
* - 公共 API:提供多种操作方法
*
* 使用示例:
* <counter-component initial="5" step="2" min="0" max="20"></counter-component>
*
* 属性说明:
* - initial: 初始值(默认:0)
* - step: 步长(默认:1)
* - min: 最小值(默认:无限制)
* - max: 最大值(默认:无限制)
*
* 事件说明:
* - counter-change: 计数器值变化时触发
* detail: { action: string, value: number, oldValue: number }
*
* 公共方法:
* - getValue(): 获取当前值
* - setValue(value): 设置新值
* - reset(): 重置为初始值
* - increment(): 增加一个步长
* - decrement(): 减少一个步长
*/
class CounterComponent extends HTMLElement {
/**
* 构造函数
* 初始化组件的基本属性和状态
*/
constructor() {
super();
// 创建 Shadow DOM,mode: 'open' 允许外部访问
this.attachShadow({ mode: 'open' });
// 计数器状态
this._value = 0; // 当前值
this._initialValue = 0; // 初始值
this._step = 1; // 步长
this._min = null; // 最小值
this._max = null; // 最大值
}
/**
* 定义需要观察的属性
* 当这些属性发生变化时,会触发 attributeChangedCallback
* @returns {string[]} 需要观察的属性名数组
*/
static get observedAttributes() {
return ['initial', 'step', 'min', 'max'];
}
/**
* 组件连接到 DOM 时调用
* 初始化组件状态、渲染界面、绑定事件
*/
connectedCallback() {
// 从属性中读取配置参数
this._initialValue = parseInt(this.getAttribute('initial')) || 0;
this._step = parseInt(this.getAttribute('step')) || 1;
this._min = this.hasAttribute('min') ? parseInt(this.getAttribute('min')) : null;
this._max = this.hasAttribute('max') ? parseInt(this.getAttribute('max')) : null;
// 设置初始值
this._value = this._initialValue;
// 渲染组件界面
this.render();
// 绑定事件监听器
this.addEventListeners();
}
/**
* 观察的属性发生变化时调用
* @param {string} name - 发生变化的属性名
* @param {string} oldValue - 变化前的值
* @param {string} newValue - 变化后的值
*/
attributeChangedCallback(name, oldValue, newValue) {
// 只有当值真正发生变化时才处理
if (oldValue !== newValue) {
switch (name) {
case 'initial':
this._initialValue = parseInt(newValue) || 0;
break;
case 'step':
this._step = parseInt(newValue) || 1;
break;
case 'min':
this._min = this.hasAttribute('min') ? parseInt(newValue) : null;
break;
case 'max':
this._max = this.hasAttribute('max') ? parseInt(newValue) : null;
break;
}
// 重新渲染界面
this.render();
}
}
/**
* 绑定事件监听器
* 为按钮添加点击事件处理
*/
addEventListeners() {
const shadow = this.shadowRoot;
// 增加按钮事件监听
const incrementBtn = shadow.querySelector('.increment-btn');
if (incrementBtn) {
incrementBtn.addEventListener('click', () => {
this.increment();
});
}
// 减少按钮事件监听
const decrementBtn = shadow.querySelector('.decrement-btn');
if (decrementBtn) {
decrementBtn.addEventListener('click', () => {
this.decrement();
});
}
// 重置按钮事件监听
const resetBtn = shadow.querySelector('.reset-btn');
if (resetBtn) {
resetBtn.addEventListener('click', () => {
this.reset();
});
}
}
/**
* 渲染组件界面
* 根据当前状态渲染计数器的显示内容
*/
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.counter-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
color: white;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
min-width: 200px;
}
.counter-display {
font-size: 3rem;
font-weight: 300;
text-align: center;
margin: 0;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.counter-info {
font-size: 0.9rem;
opacity: 0.8;
text-align: center;
margin: 0;
}
.counter-controls {
display: flex;
gap: 10px;
align-items: center;
}
.counter-btn {
background: rgba(255,255,255,0.2);
border: 2px solid rgba(255,255,255,0.3);
color: white;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 1.2rem;
font-weight: 600;
transition: all 0.3s ease;
min-width: 50px;
}
.counter-btn:hover {
background: rgba(255,255,255,0.3);
border-color: rgba(255,255,255,0.5);
transform: translateY(-2px);
}
.counter-btn:active {
transform: translateY(0);
}
.counter-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.increment-btn {
background: rgba(76, 175, 80, 0.8);
border-color: rgba(76, 175, 80, 0.9);
}
.decrement-btn {
background: rgba(244, 67, 54, 0.8);
border-color: rgba(244, 67, 54, 0.9);
}
.reset-btn {
background: rgba(255, 193, 7, 0.8);
border-color: rgba(255, 193, 7, 0.9);
font-size: 1rem;
}
(max-width: 480px) {
.counter-container {
padding: 15px;
min-width: 180px;
}
.counter-display {
font-size: 2.5rem;
}
.counter-controls {
flex-direction: column;
gap: 8px;
}
.counter-btn {
width: 100%;
min-width: auto;
}
}
</style>
<div class="counter-container">
<h2 class="counter-display">${this._value}</h2>
<p class="counter-info">
步长: ${this._step}
${this._min !== null ? `<br>最小值: ${this._min}` : ''}
${this._max !== null ? `<br>最大值: ${this._max}` : ''}
</p>
<div class="counter-controls">
<button class="counter-btn decrement-btn"
${this._min !== null && this._value <= this._min ? 'disabled' : ''}>
-
</button>
<button class="counter-btn reset-btn">重置</button>
<button class="counter-btn increment-btn"
${this._max !== null && this._value >= this._max ? 'disabled' : ''}>
+
</button>
</div>
</div>
<slot name="footer"></slot>
`;
// 重新绑定事件监听器
this.addEventListeners();
}
// 公共方法
/**
* 获取当前计数器值
* @returns {number} 当前值
*/
getValue() {
return this._value;
}
/**
* 设置计数器值
* @param {number} value - 要设置的新值
*/
setValue(value) {
const oldValue = this._value;
const newValue = parseInt(value);
// 检查边界条件
if (this._min !== null && newValue < this._min) {
this._value = this._min;
} else if (this._max !== null && newValue > this._max) {
this._value = this._max;
} else {
this._value = newValue;
}
// 触发值变化事件
this.dispatchEvent(new CustomEvent('counter-change', {
detail: {
action: 'set',
value: this._value,
oldValue: oldValue
},
bubbles: true,
composed: true
}));
// 重新渲染界面
this.render();
}
/**
* 重置计数器为初始值
*/
reset() {
const oldValue = this._value;
this._value = this._initialValue;
// 触发值变化事件
this.dispatchEvent(new CustomEvent('counter-change', {
detail: {
action: 'reset',
value: this._value,
oldValue: oldValue
},
bubbles: true,
composed: true
}));
// 重新渲染界面
this.render();
}
/**
* 增加计数器值
*/
increment() {
const oldValue = this._value;
const newValue = this._value + this._step;
// 检查最大值限制
if (this._max !== null && newValue > this._max) {
this._value = this._max;
} else {
this._value = newValue;
}
// 触发值变化事件
this.dispatchEvent(new CustomEvent('counter-change', {
detail: {
action: 'increment',
value: this._value,
oldValue: oldValue
},
bubbles: true,
composed: true
}));
// 重新渲染界面
this.render();
}
/**
* 减少计数器值
*/
decrement() {
const oldValue = this._value;
const newValue = this._value - this._step;
// 检查最小值限制
if (this._min !== null && newValue < this._min) {
this._value = this._min;
} else {
this._value = newValue;
}
// 触发值变化事件
this.dispatchEvent(new CustomEvent('counter-change', {
detail: {
action: 'decrement',
value: this._value,
oldValue: oldValue
},
bubbles: true,
composed: true
}));
// 重新渲染界面
this.render();
}
}
// 导出组件类
export default CounterComponent;