wired-elements 手绘风日历组件 wired-calendar 实战指南:属性、事件与源码实现解析
2026/9/24 11:59:14 网站建设 项目流程
  • UI组件
  • 前端

【免费下载链接】wired-elements

Collection of custom elements that appear hand drawn. Great for wireframes or a fun look.

项目地址:https://gitcode.com/gh_mirrors/wi/wired-elements
点击查看免费下载

wired-calendar是 wired-elements 组件库中一款采用手绘线框风格(hand-drawn wireframe)渲染的日历选择器,非常适合用于原型(wireframe)设计或追求趣味性的界面。本指南以官方文档 docs/wired-calendar.md 为主体,结合仓库源码 src/wired-calendar.ts 与可运行示例 examples/calendar.html,带你掌握 wired-calendar 的安装引入、全部属性与事件、CSS 变量定制方法,并深入理解其背后的 roughjs 手绘渲染与日期计算原理。

一、wired-calendar 是什么

wired-elements 是一组"看起来像手绘草图"的自定义元素(Custom Elements),官方 README 将其定位为Collection of custom elements that appear hand drawn. Great for wireframes or a fun look.。wired-calendar 正是其中用于日期选择的组件:它在一个手绘矩形卡片内呈现单月日历,支持月份切换、日期范围限制、禁用态、多语言表头以及基于红色草图圆圈的选中态高亮。

它的典型应用场景包括:

  • 原型阶段快速搭建带日期选择的线框图;
  • 需要视觉趣味性的个人项目或内部工具;
  • 作为学习 LitElement + roughjs 手绘组件实现思路的参考案例。

二、安装与引入

2.1 通过 npm 安装

将 wired-elements 添加到 JavaScript 项目中:

npm i wired-elements

安装完成后,当前仓库对应的包信息见 package.json,组件基于lit(^2.0.0-rc.1)与roughjs(^4.3.1)构建,构建产物输出到lib/目录。

2.2 按需导入模块

在代码中导入WiredCalendar类:

import { WiredCalendar } from 'wired-elements/lib/wired-calendar.js';

说明:虽然 src/wired-elements.ts 提供了聚合入口(main/module均指向lib/wired-elements.js),但从该文件的 re-export 列表可以推断,wired-calendar并未包含在聚合入口中,因此官方文档推荐直接导入单个模块文件lib/wired-calendar.js

2.3 通过 CDN 直接加载

也可以将模块脚本直接加载到 HTML 页面中:

<script type="module" src="https://unpkg.com/wired-elements/lib/wired-calendar.js?module"></script>

2.4 在 HTML 中使用

引入后在页面中放置标签即可:

<wired-calendar selected="Jul 4, 2019"> </wired-calendar>

这是文档给出的最小可用用法:传入selected="Jul 4, 2019"后,日历会自动预选中该日期。仓库示例 examples/calendar.html 中还展示了更丰富的组合,例如同时设置日期范围、多语言与initials

<wired-calendar id="calendar2" elevation="1" firstdate="Apr 15, 2019" lastdate="Jul 15, 2019" selected="Jul 4, 2019" locale="fr" initials> </wired-calendar>

三、属性(Properties)详解

以下为文档列出的全部属性,并结合源码逐项深入说明。

属性类型默认值说明
elevationNumber源码默认 3(文档标注 1)1–5(含)之间的数字,设置日历卡片的立体高度
selectedString可选,可被Date解析的字符串,预选并高亮某个日期
firstdateString可选,可被Date解析的字符串,有效日期的下限
lastdateString可选,可被Date解析的字符串,有效日期的上限
localeString浏览器 locale仅用于渲染表头的 BCP 47 语言标签(如es-MXfrde
disabledBooleanfalse禁用日历选择器
initialsBooleanfalse星期几使用首字母(如SM)而非缩写(如Sun
valueObject包含选中Date对象及对应格式化文本的 JavaScript 对象
formatFunction见下文获取/设置将Date对象格式化为文本的 JavaScript 函数

源码中对应的属性声明位于 src/wired-calendar.ts。

3.1 elevation:手绘卡片的"厚度"

elevation控制日历外框手绘阴影的层数。源码在updated()中对取值做了钳制处理:

const elev = Math.min(Math.max(1, this.elevation), 5);

即超出 1–5 范围的值会被强制收敛到合法区间。绘制时每增加 1 层,会在矩形右下侧叠加一条透明度递减的手绘线(opacity从 85% 起每层递减 10%),从而形成手绘纸片的立体感。

注意:文档标注 elevation 默认值为 1,但当前仓库源码 src/wired-calendar.ts 中声明为elevation = 3,使用时请以实际源码版本为准。

3.2 selected:预选日期

selected接受任何能被 JavaScriptDate构造函数解析的字符串,例如"Jul 4, 2019"。组件在初始化时会:

  1. new Date(this.selected)解析出日期;
  2. 以该日期所在月份作为当前展示月份(firstOfMonthDate取当月 1 日);
  3. refreshSelection()中通过字符串比较day.value === this.selected逐格标记选中状态(src/wired-calendar.ts)。

由于选中匹配是字符串级比较,因此传入的selected字符串应尽量与format的输出格式保持一致,才能保证正确高亮。默认format输出形如"Jul 4, 2019"(月份英文简称 + 日 + 年)。

3.3 firstdate / lastdate:日期范围限制

这两个属性分别限定可选日期的下界与上界,解析后保存在私有变量fDate/lDate中(src/wired-calendar.ts)。其作用体现在两个层面:

  • 日期禁用isDateOutOfRange(day)(src/wired-calendar.ts)判断某天是否越界。同时给出上下界时,day < fDate || lDate < day即判定越界;只给出其中一侧时按单侧判断。越界的日期单元格会被标记为disabled,点击不生效;
  • 月份导航限制onPrevClick()/onNextClick()在翻页前会检查前一月/下一月是否会越过fDate/lDate所在月份,从而阻止用户翻出有效范围。

3.4 locale:仅作用于表头渲染

locale是 BCP 47 语言标签(如es-MXfrde)。文档特别强调:该属性只用于渲染日历表头(月份名与星期名),所有内部和外部的日期处理不受 locale 影响。

源码localizeCalendarHeaders()(src/wired-calendar.ts)的实现印证了这一点:

  • 未显式设置时,依次探测navigator.systemLanguagenavigator.browserLanguagenavigator.languages,兜底为en
  • 当 locale 不是en/en-US时,使用Date.prototype.toLocaleString(locale, { weekday: 'short' }){ month: 'long' }重新生成本地化表头文本;
  • 关键的内部日期比较所用的months_short数组(如JanFeb…)保持en-US不变,注释明确警告 "month shorts are used inen-USinternally. Do not change."。

因此,无论界面显示什么语言,format输出与selected匹配逻辑始终基于英文月份简称,这也是示例注释中特别说明"参数日期不受 locale 影响"的原因。

3.5 disabled:整体禁用

disabledtrue时,组件添加wired-disabled类,表现为半透明(opacity: 0.5)、pointer-events: none、鼠标光标变为默认,同时tabIndex被设为 -1,从 Tab 键序中移除;恢复时还原(src/wired-calendar.ts)。需要说明的是,这与 3.3 节中单日 disabled 不同:前者禁用整个组件,后者只禁用范围外的日期。

3.6 initials:星期首字母模式

initialstrue时,星期表头只显示首字母(如SMT…),否则显示weekdays_short数组中的短名称(如SunMon)。该判断发生在渲染阶段:this.initials ? d[0] : d(src/wired-calendar.ts)。

3.7 value:选中结果对象

value是一个包含两个字段的 JavaScript 对象:

{ date: Date, // 被选中的 Date 对象 text: string // 对应的格式化文本(由 format 函数输出) }

用户每次选中日期后,组件会更新value并触发selected事件,因此这是读取当前选中结果的主要途径(详见第五节)。

3.8 format:自定义日期格式化函数

format属性可读写,用于控制日期文本的呈现。默认实现为:

(d: Date) => this.months_short[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear()

即输出"Jul 4, 2019"这类格式。它同时参与两件事:

  • 生成每个日期单元格的value字符串(用于选中匹配);
  • 作为value.text的内容。

示例 examples/calendar.html 展示了配合使用方式:myCalendar4.format(today)先生成今天的格式化文本,再交给公开方法setSelectedDate()完成程序化选中。

3.9 公开方法 setSelectedDate()

文档未单独列出但示例中实际使用的方法是setSelectedDate(formatedDate)(src/wired-calendar.ts)。传入一个日期字符串即可:

myCalendar4.setSelectedDate('Jul 4, 2019');

方法内部会更新selected、将视图定位到对应月份、重算日历并触发selected事件,适合在按钮或其他控件中做程序化日期选择。

四、自定义 CSS 变量

文档定义了 4 个可用于主题化的 CSS 变量:

变量作用默认值
--wired-calendar-bg日历背景色白色
--wired-calendar-color日历手绘线条颜色黑色
--wired-calendar-selected-color选中日期的草图圆圈颜色红色
--wired-calendar-dimmed-color不属于当月的"灰显"日期字体颜色灰色

此外,从源码样式(src/wired-calendar.ts)可以发现第 5 个可用变量:--wired-calendar-disabled-color(默认lightgray),用于超出firstdate/lastdate范围的禁用日期字体颜色,文档未列出但实际可用。

示例 examples/calendar.html 中给出了完整的自定义样式写法:

.custom { --wired-calendar-bg: yellow; --wired-calendar-color: red; --wired-calendar-selected-color: black; --wired-calendar-dimmed-color: brown; width: 260px; height: 260px; font-size: 18px; }

注意:除颜色变量外,组件的最终渲染尺寸还受元素本身width/heightfont-size影响(见 6.2 节的尺寸计算逻辑)。

五、事件(Events)

文档定义了唯一的事件:

  • selected:当用户(或通过setSelectedDate)选中某个日期时触发。

从源码 src/wired-calendar.ts 可以看到事件派发细节:

this.value = { date: new Date(this.selected), text: this.selected }; fireEvent(this, 'selected', { selected: this.selected });

其中fireEvent来自 src/wired-base.ts,派发的是标准CustomEvent,并带有bubbles: truecomposed: true,因此事件会冒泡且能穿透 Shadow DOM,在外部直接监听即可。

监听方式:

myCalendar4.addEventListener('selected', () => { const selectedObject = myCalendar4.value; // selectedObject.date 是 JavaScript Date 对象 // selectedObject.text 是格式化后的日期文本 console.log(selectedObject.text); });

六、完整实战示例

仓库中的 examples/calendar.html 是官方提供的完整演示,包含三种静态用法和一组带 JavaScript 交互的用法。其中交互部分的核心逻辑如下:

<wired-calendar id="calendar4" elevation="5" firstdate="Apr 15, 2019" lastdate="Jul 15, 2019" locale="es-MX" initials> </wired-calendar> <p id="calendar4-result">Select a date in the calendar</p> <wired-button id="btn-today">Today</wired-button> <wired-button id="btn-update">Update</wired-button> <p id="calendar4-update">No updated yet</p>
const myCalendar4 = document.getElementById('calendar4'); // 方式一:事件驱动 —— 监听 selected 事件读取 value myCalendar4.addEventListener('selected', () => { let selectedObject = myCalendar4.value; // selectedObject.date 为 Date 对象;selectedObject.text 为格式化文本 let formatedDate = selectedObject.text; document.getElementById('calendar4-result').innerHTML = formatedDate + ' <br><small>Note: Internal date handling not affected by locale.</small>'; }); // 方式二:非事件驱动 —— 通过 value 属性轮询读取 document.getElementById('btn-update').addEventListener('click', () => { const selectedObject = myCalendar4.value; if (selectedObject && selectedObject.date) { myCalendar4update.innerHTML = selectedObject.date.toLocaleDateString(); } else { myCalendar4update.innerHTML = 'No date selected yet.'; } }); // 方式三:程序化设置 —— 使用 format + setSelectedDate document.getElementById('btn-today').addEventListener('click', () => { let today = new Date(); let formatedDate = myCalendar4.format(today); // 传入任何 JavaScript Date 可解析的格式均可 myCalendar4.setSelectedDate(formatedDate); });

该示例同时展示了三种典型交互路径:监听事件直接读取value属性调用公开方法程序化设值,覆盖了组件对外的主要编程接口。

七、源码实现解析:手绘日历是如何画出来的

7.1 整体渲染结构

组件基于 LitElement,内部模板由一个<table>和两个 SVG overlay 构成(src/wired-calendar.ts):

  • 第一行表头:<<上一月 / 月份+年份标题 />>下一月;
  • 第二行表头:七个星期名(initials为 true 时取首字母);
  • 数据区:按周循环生成<tr>,每周 7 个<td>日期单元格;
  • 日历外框:覆盖在表格之上的svg.calendar,用于绘制手绘矩形边框与立体边线;
  • 选中态:选中单元格内部再嵌一个svg.selected,用于绘制手绘红色椭圆。

7.2 roughjs 手绘绘制

所有"手绘感"都来自 src/wired-lib.ts 对 roughjs 的封装。以日历外框为例,updated()中(src/wired-calendar.ts)先清空 SVG,再用rectangle()绘制粗糙矩形边框,随后按elevation叠加半透明边线形成立体厚度;选中日期则用ellipse()画一个红色手绘椭圆(stroke-width: 2.5)圈住数字。每个元素都使用组件初始化时生成的随机种子(randomSeed(),见 src/wired-base.ts),保证每次刷新后线条抖动形状一致。

7.3 尺寸计算

  • getCalendarSize()(src/wired-calendar.ts)读取元素实际包围盒,若宽或高小于 180px 则回退为 320px,保证最小可读尺寸;
  • computeCellsizes()(src/wired-calendar.ts)按比例分配空间:两行表头合计占高度的 25%,剩余 75% 按周数均分给每周;列宽为总宽除以 7 再减去 2px 边框间距;
  • 组件在connectedCallback()中注册了 200ms 防抖的resize监听,窗口变化时自动重算(src/wired-calendar.ts)。

7.4 月份网格计算

computeCalendar()(src/wired-calendar.ts)以当月 1 日往前偏移到周日为起点,按 7 天为一周循环填充整月网格;不属于当月的日期标记为dimmed(灰显);同时为每个单元格生成value(格式化日期字符串)、text(日数字)与disabled(是否越界)标记。monthYear表头文本随firstOfMonthDate变化。

7.5 生命周期与禁用态

  • 组件设置role="dialog"角色(firstUpdated());
  • connectedCallback()中依次完成表头本地化、初始条件设置、日历计算与选中刷新,随后通过setTimeout(() => this.updated())触发首次手绘渲染;disconnectedCallback()中移除 resize 监听;
  • 禁用态通过wired-disabled类实现(opacity: 0.5pointer-events: none),焦点态下 SVG 线条加粗至 1.5。

八、注意事项与常见问题

  1. 日期字符串格式selectedfirstdatelastdate接受任何 JavaScriptDate可解析的格式,但selected与默认format输出做字符串级匹配,建议保持格式一致,例如"Jul 4, 2019"
  2. locale 只影响外观:无论locale设为哪种语言,内部日期解析、格式化与比较始终基于固定的英文月份简称,这是设计上的刻意行为;
  3. elevation 范围:虽然文档标注 1–5,源码会对超出范围的值自动钳制到 1–5,但传入合法值仍是推荐做法;
  4. 默认值差异:文档中 elevation 默认值标注为 1,而当前仓库源码默认值为 3,请以所安装版本的实际行为为准;
  5. 禁用语义disabled禁用整个组件,firstdate/lastdate只禁用范围外的日期,两者可组合使用。

九、深入阅读

  • 组件官方文档:docs/wired-calendar.md
  • 核心源码实现:src/wired-calendar.ts
  • 可运行示例:examples/calendar.html
  • 手绘绘制封装(roughjs):src/wired-lib.ts
  • 事件与随机种子工具:src/wired-base.ts
  • 项目构建与依赖配置:package.json

wired-calendar 以 MIT 协议开源(详见仓库 LICENSE),其文档贡献者包括 Eduardo Martinez。结合本文的属性说明、示例代码与源码解析,你可以直接在页面中嵌入一个手绘风格的日期选择器,并通过selected事件、value属性与 CSS 变量快速接入业务逻辑与视觉定制。

  • UI组件
  • 前端

【免费下载链接】wired-elements

Collection of custom elements that appear hand drawn. Great for wireframes or a fun look.

项目地址:https://gitcode.com/gh_mirrors/wi/wired-elements
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询