实现页面多处独立库存计数器:使用Web Components的解决方案


实现页面多处独立库存计数器:使用Web Components的解决方案

本文介绍如何使用Web Components(自定义元素)解决同一页面上显示多个独立库存计数器的问题。通过创建自定义元素,每个计数器拥有独立的初始数量和持久化存储键,确保它们的状态互不影响,并能各自进行倒计时更新,极大提升了组件的复用性和可维护性。

1. 问题背景与分析

在网页中,我们可能需要在同一页面上展示多个产品或计划的库存倒计时,并且每个倒计时都应独立运行,拥有各自的初始数量和状态持久化能力。原始的j*ascript脚本通常通过document.getelementbyid('qty')来获取显示库存的元素,并通过localstorage.setitem('s*ed_countdown', qty)来保存库存值。这种方法存在两个核心问题:

  1. ID冲突与单一目标: document.getElementById()方法只会返回文档中第一个匹配指定ID的元素。当页面上存在多个具有相同ID(如qty)的元素时,脚本只会更新第一个元素。即使通过修改ID为qty1、qty2等并复制脚本,如果脚本内部仍然引用全局的qtySpan变量或未正确为每个实例绑定其对应的DOM元素,仍可能导致只有一个计数器生效。
  2. 全局状态共享: localStorage.setItem('s*ed_countdown', qty)使用了固定的键名s*ed_countdown。这意味着所有复制的脚本实例都会尝试读写同一个localStorage键,导致它们的库存状态相互覆盖,无法独立持久化。

为了解决这些问题,我们需要一种更模块化、可复用且能够封装自身状态的解决方案。Web Components中的自定义元素(Custom Elements)正是为此而生。

2. 解决方案:利用自定义元素实现独立库存计数器

通过将库存计数器封装为一个自定义元素,我们可以实现完全独立的多个计数器实例。每个实例将拥有自己的属性来管理初始数量和持久化存储键,从而避免全局冲突。

2.1 自定义元素 的设计

我们设计一个名为的自定义元素,它将具有以下两个关键属性:

  • quantity: 表示计数器开始倒计时的初始数量。如果localStorage中存在对应的值,该值将优先于此属性。
  • storage-key: 一个可选的字符串,用于指定该计数器在localStorage中存储其最新数量的唯一键名。如果未设置此属性,则该计数器将不会持久化其状态。

2.2 自定义元素实现代码

以下是自定义元素的完整实现代码:

customElements.define('stock-counter', class extends HTMLElement {
  // 获取当前库存数量的getter
  get quantity() {
    // 检查是否设置了storageKey,并尝试从localStorage获取存储值
    if (this.storageKey !== null) {
      const value = Number(localStorage.getItem(this.storageKey));
      // 如果存储值是有效数字且不为0,则使用存储值
      if (!Number.isNaN(value) && value !== 0) {
        return value;
      }
    }

    // 否则,从元素的quantity属性获取初始值
    const value = Number(this.getAttribute('quantity'));
    // 如果属性值不是有效数字,则返回0
    if (Number.isNaN(value)) {
      return 0;
    }
    return value;
  }

  // 设置库存数量的setter
  set quantity(value) {
    if (!isNaN(value)) {
      // 如果设置了storageKey,则将新值存储到localStorage
      if (this.storageKey !== null) {
        localStorage.setItem(this.storageKey, value);
      }
      // 更新元素的quantity属性
      this.setAttribute('quantity', value);
      // 更新元素的文本内容以显示最新数量
      this.textContent = value; // 确保在设置时也更新显示
    }
  }

  // 获取storage-key属性的getter
  get storageKey() {
    return this.getAttribute('storage-key');
  }

  // 当元素被添加到文档DOM时调用
  connectedCallback() {
    // 初始化显示数量
    this.textContent = this.quantity;
    // 启动计数器
    this.count();
  }

  // 核心计数逻辑
  count = () => {
    let qty = this.quantity; // 获取当前数量

    if (qty === 0) {
      this.textContent = 0; // 确保显示为0
      return; // 数量为0时停止计数
    }

    // 随机减少1到3个单位
    let parts = Math.floor((Math.random() * 3) + 1);
    // 确保减少的数量不超过当前库存
    if (parts > qty) {
      parts = qty;
    }

    // 更新库存数量(通过setter会自动更新localStorage和属性)
    this.quantity -= parts;

    // 随机延迟15到30秒后再次计数
    const msec = Math.floor(((Math.random() * 15) + 15) * 1000);
    setTimeout(this.count, msec);
  };
});

2.3 代码详解

  • customElements.define('stock-counter', class extends HTMLElement { ... });
    • 这是注册自定义元素的关键。它将stock-counter标签与我们定义的J*aScript类关联起来。
  • get quantity():
    • 这是一个计算属性。它首先检查是否存在storage-key。如果存在,它会尝试从localStorage中获取对应的值。
    • 如果localStorage中的值是有效的数字且不为0,则优先使用该值,实现了状态的持久化。
    • 否则,它会回退到从元素的quantity属性获取初始值。
    • Number.isNaN()用于判断转换后的值是否为非数字,确保返回有效数字。
  • set quantity(value):
    • 这是一个设置属性的setter。每当this.quantity被赋值时,这个方法就会被调用。
    • 它负责将更新后的值存储到localStorage(如果storage-key存在)。
    • 同时,它也会更新元素的quantity属性,并更新元素的textContent,从而在页面上实时显示最新的数量。
  • get storageKey():
    • 简单地返回storage-key属性的值。
  • connectedCallback():
    • 这是自定义元素的生命周期回调之一,当元素首次被添加到文档DOM时调用。
    • 在这里,我们初始化元素的文本内容为当前数量,并调用count()方法启动倒计时逻辑。
  • count():
    • 这是核心的倒计时逻辑。它是一个箭头函数,确保this始终指向当前自定义元素实例。
    • 获取当前数量,如果为0则停止。
    • 随机计算要减少的数量,并确保不会减到负数。
    • 通过this.quantity -= parts更新数量。由于quantity是一个setter,这会自动处理localStorage的更新和页面显示。
    • 使用setTimeout以随机延迟再次调用自身,实现持续倒计时。

3. 如何使用自定义元素

一旦自定义元素被定义,你就可以像使用任何标准HTML标签一样在页面中使用它。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>多处独立库存计数器示例</title>
</head>
<body>

    <h1>产品库存状态</h1>

    <div>
        <h2>产品 A</h2>
        <p>剩余库存:<stock-counter quantity="40" storage-key="countdown-one">40</stock-counter> 件</p>
                    <div class="aritcle_card">
                        <a class="aritcle_card_img" href="/xiazai/learn/2528">
                            <img src="https://img.php.cn/upload/webcode/000/000/000/5a28fc979bf0e712.png" alt="PHP经典实例(第二版)">
                        </a>
                        <div class="aritcle_card_info">
                            <a href="/xiazai/learn/2528">PHP经典实例(第二版)</a>
                            <p>PHP经典实例(第2版)能够为您节省宝贵的Web开发时间。有了这些针对真实问题的解决方案放在手边,大多数编程难题都会迎刃而解。《PHP经典实例(第2版)》将PHP的特性与经典实例丛书的独特形式组合到一起,足以帮您成功地构建跨浏览器的Web应用程序。在这个修订版中,您可以更加方便地找到各种编程问题的解决方案,《PHP经典实例(第2版)》中内容涵盖了:表单处理;Session管理;数据库交互;使用We</p>
                            <div class="">
                                <img src="/static/images/card_xiazai.png" alt="PHP经典实例(第二版)">
                                <span>453</span>
                            </div>
                        </div>
                        <a href="/xiazai/learn/2528" class="aritcle_card_btn">
                            <span>查看详情</span>
                            <img src="/static/images/cardxiayige-3.png" alt="PHP经典实例(第二版)">
                        </a>
                    </div>
                
    </div>

    <div>
        <h2>产品 B (无持久化)</h2>
        <p>剩余库存:<stock-counter quantity="50">50</stock-counter> 件</p>
    </div>

    <div>
        <h2>产品 C</h2>
        <p>剩余库存:<stock-counter quantity="80" storage-key="countdown-three">80</stock-counter> 件</p>
    </div>

    <script>
        // 将自定义元素的定义代码放在这里,或者引入一个外部JS文件
        customElements.define('stock-counter', class extends HTMLElement {
          get quantity() {
            if (this.storageKey !== null) {
              const value = Number(localStorage.getItem(this.storageKey));
              if (!Number.isNaN(value) && value !== 0) {
                return value;
              }
            }
            const value = Number(this.getAttribute('quantity'));
            if (Number.isNaN(value)) {
              return 0;
            }
            return value;
          }

          set quantity(value) {
            if (!isNaN(value)) {
              if (this.storageKey !== null) {
                localStorage.setItem(this.storageKey, value);
              }
              this.setAttribute('quantity', value);
              this.textContent = value; // 确保在设置时也更新显示
            }
          }

          get storageKey() {
            return this.getAttribute('storage-key');
          }

          connectedCallback() {
            this.textContent = this.quantity; // 初始化显示
            this.count();
          }

          count = () => {
            let qty = this.quantity;
            if (qty === 0) {
              this.textContent = 0;
              return;
            }
            let parts = Math.floor((Math.random() * 3) + 1);
            if (parts > qty) {
              parts = qty;
            }
            this.quantity -= parts;
            const msec = Math.floor(((Math.random() * 15) + 15) * 1000);
            setTimeout(this.count, msec);
          };
        });
    </script>

</body>
</html>

在上面的示例中:

  • 产品 A 的计数器从40开始,并将状态持久化到localStorage中名为countdown-one的键。
  • 产品 B 的计数器从50开始,但由于没有storage-key属性,它的状态不会被持久化,每次页面刷新都会重置为50。
  • 产品 C 的计数器从80开始,并将状态持久化到localStorage中名为countdown-three的键。

每个实例都将独立运行,互不干扰,并且带有storage-key的实例在页面刷新后会从上次保存的状态继续倒计时。

4. 注意事项与总结

  • 浏览器兼容性: 现代浏览器(Chrome, Firefox, Edge, Safari)对Custom Elements的支持良好。对于旧版浏览器,可能需要引入Web Components Polyfills。
  • 命名规范: 自定义元素的标签名必须包含一个连字符(-),例如stock-counter,以避免与现有或未来的HTML标签冲突。
  • 封装性: 自定义元素提供了强大的封装能力。每个实例都拥有自己的内部状态和行为,不会泄露到全局作用域,也不会与其他组件冲突。
  • 可维护性与可读性: 通过将复杂的J*aScript逻辑封装在自定义元素中,HTML变得更加声明性(declarative)和易于理解。开发人员只需关注元素的属性即可配置其行为,而无需深入了解其内部实现。
  • 扩展性: 可以根据需求为自定义元素添加更多的属性、方法或事件,以实现更复杂的功能,例如在库存为零时触发一个事件。

通过采用Web Components的自定义元素,我们优雅地解决了同一页面上多个独立库存计数器的实现难题,提供了一个可复用、可维护且高性能的解决方案。

以上就是实现页面多处独立库存计数器:使用Web Components的解决方案的详细内容,更多请关注其它相关文章!


# 自己的  # 临沧seo运营培训学校  # 网站建设频教程  # 北戴河区自制网站建设  # 阿克苏求职网站建设  # 湖南哪个网站推广好点呢  # 晋州个人网站推广  # 涉县网络营销推广平台  # 佛山网站建设制作  # seo新闻外链管用吗  # 商业网站建设品牌大全  # 这是一个  # 复用  # 第一个  # 放在  # javascript  # 多处  # 这是  # 多个  # 倒计时  # 自定义  # 封装性  # 持久化存储  # 作用域  # safari  # edge  # 浏览器  # js  # html  # java 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: vivo手机视频通话美颜怎么设置_vivo视频通话美颜开启方法  word文档行距怎么调?word文档调行距的操作步骤  word邮件合并怎么插入个性化图片_Word邮件合并插入个性化图片方法  感染了幽门螺杆菌一定会导致胃癌吗?蚂蚁庄园今日答案最新11.30  《杖剑传说》食谱大全  在Flask应用中安全高效地更新SQLAlchemy用户数据  C#中的Record类型有什么优势?C# 9新特性Record与Class的用法区别  邮政快递寄件查询入口 邮政快递收件查询入口  Lar*el怎么实现全文搜索_Lar*el Scout集成Algolia教程  《KARDS》冬季扩展包“国土阵线”上线!全新“协力”机制改变战场格局  多闪APP官方下载安装入口_多闪最新版本获取入口  12306夜间购票失败? | 查看官方公布的暂停服务公告与应对方案  mysql如何管理数据库账户_mysql数据库账户管理技巧  手机雨课堂网页版入口免登录 雨课堂网页版可点击直接进入  Win11怎么开启HDR_Windows 11显示器画质增强设置  J*a实现任务清单管理_集合框架综合入门练手  精通VS Code多光标编辑以实现闪电般快速的修改  Go App Engine 项目结构与包管理深度指南  天堂漫画网页版在线阅读 天堂漫画手机版入口  Final Cut Pro视频加EQ教程  抖音号升级成企业资质怎么弄?有什么好处?  word文档中的分隔符有哪些不同类型和用途_Word分隔符类型与用途方法  C++如何实现单例模式_C++线程安全的单例模式写法  excel怎么制作考勤表 excel考勤模板与函数公式讲解  Win10如何彻底关闭OneDrive Win10禁用云同步功能【纯净】  《三国:谋定天下》平民全阶段通用阵容  快递物流路径揭秘  Linux如何自动分析系统异常日志_Linux日志智能检测  曝《丝之歌》DLC有望开发!开发商还有神秘新企划  折叠屏手机充不进电是什么问题? 特殊结构带来的维修难点  德邦快递查询入口登录官网 德邦快递单号查询系统入口  如何测试您的网站全球打开速度-网站海外测速工  汽水音乐在线听歌网页版 汽水音乐在线听歌网页版入口  德邦物流在线查询系统 德邦快递货物运输追踪  12306APP选座怎么选充电位置_12306APP带充电插座座位选择方法与技巧  PPT智能排版生成入口 免费PPT内容自动生成平台  LocoySpider如何批量采集电商商品_LocoySpider电商采集的模板应用  windows10怎么设置电源按钮_windows10按下电源键功能修改  青橙手机语音助手怎么唤醒_青橙手机语音助手设置与唤醒方法  百度地图离线地图无法加载如何解决 百度地图离线地图加载优化方法  Lar*el 关联查询:同时筛选父表与子表数据的高效策略  第五人格PC版怎么避免被封号_第五人格PC版防封号注意事项  知乎APP怎么查看自己被邀请的问题_知乎APP邀请回答记录查看与参与方法  如何在 WordPress 前端实现内容提交:古腾堡编辑器的替代方案与实践  C++ optional用法详解_C++17处理可能为空的返回值  iCloud官方网站 iCloud网页版在线登录入口  深入理解J*aScript异步操作:setTimeout与调用栈的真相  PSD转AI文件的简单方法  J*aScript 数值去小数位处理:多种方法与实践  歌词怎么展示在|直播|间视频号?有什么注意事项? 

 2025-10-07

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.