Guide.md 13 KB

XJS 项目开发指南(Guide)

本仓库的目标是做一套可用于生产环境的 JS + CSS 框架,最终在业务项目中只需要引入:

  • xjs.js(单文件 JS)
  • xjs.css(单文件 CSS)

但在本仓库内,为了保持可维护性,开发源码仍然分散在多个文件中(例如 animal.jslayer.js),再通过构建脚本合并为单文件产物。

本文档用于统一后续完善/开发的目录约定、命名规范、编码习惯、测试方式、发布流程。请把它当成项目的“团队约定”。


1. 目录结构与职责边界

当前结构(关键文件):

  • animal.js:动画/选择器核心(Selection / animate / svg / timeline / scroll / inView 等能力)
  • layer.js:弹出层组件(UI + 交互逻辑)
  • xjs.css:库运行时需要的所有 UI 样式(例如 Layer 的 .layer-*
  • xjs.js
    • 开发阶段:加载器(按顺序加载 animal.js + layer.js
    • 同仓库内还可能包含“合并构建版本”的历史内容;开发时不要手改合并产物
  • dist/xjs.js:生产合并产物(由构建命令生成)
  • doc/index.html:统一测试/文档入口(支持点击导航直接进入示例页面)
  • main.go:构建与本地静态服务脚本

源码与产物的黄金规则

  • 只改源码animal.js / layer.js / xjs.css / doc/**
  • 不手改产物dist/xjs.js(以及任何由构建生成的合并文件)
  • 任何对外发布dist/xjs.js + xjs.css 为准

2. 本地开发 / 测试流程(推荐路径)

2.1 启动静态服务

仓库自带 Go 静态服务(默认端口 8080):

go run main.go serve

然后在浏览器打开:

  • http://localhost:8080/doc/index.html

说明:doc/index.html 会作为“测试总控台”,左侧导航/中间列表/右侧 iframe 组合用于快速打开各类测试页面。

2.2 构建生产单文件(合并)

animal.js + layer.js 合并输出到 dist/xjs.js

go run main.go build

或指定输出路径:

go run main.go build path/to/xjs.js

3. 对外 API / 全局变量规范

3.1 全局导出(必须稳定)

对外承诺以下全局变量:

  • window.$:主入口(jQuery 风格选择器 + 链式能力)
  • window.xjs:兼容别名(与 $ 等价)
  • window.animal:兼容别名(与 $ 等价)
  • window.Layer:Layer 构造/工厂入口

3.2 API 设计原则(统一风格)

  • 单入口:所有能力尽量从 $(selector) 返回的 Selection 链式对象进入
  • 强约束、少魔法:避免“隐式全局配置”;如需全局配置,集中在 $.configxjs/animal 为别名)
  • 兼容性优先级:能用 WAAPI 就用 WAAPI;不支持时优雅降级(必要时 fallback 到 requestAnimationFrame)
  • 返回值规范
    • 异步行为返回 Promise(例如 animate().then(...)
    • 链式/操作型方法返回 Selection 以支持链式调用(例如 .layer(...).addClass(... ) 未来扩展)

4. JS 命名规范与编码约定

4.1 文件/模块命名

  • 文件名:全小写 + 下划线/无分隔(当前采用 animal.jslayer.js,新增模块保持一致)
  • 模块边界清晰
    • animal.js:核心选择器与动画体系(不直接引入 UI 样式)
    • layer.js:UI 组件(允许依赖 xjs.css

4.2 变量/函数命名

  • 函数camelCase
  • PascalCase
  • 常量UPPER_SNAKE_CASE
  • 私有/内部约定:以下划线前缀标识(例如 _fire_onKeydown

4.3 选择器与 Selection 约定

  • $(selector) 返回 Selection(继承 Array 的可链式集合)
  • Selection 实例方法命名尽量用动词:animate / draw / inViewAnimate / layer / unlayer
  • 新增能力优先做成 Selection 方法,而不是新增全局函数

4.4 参数命名与可扩展性

  • Options 对象:使用 plain object(JSON 友好)
  • 动画参数:把“动画属性”和“控制参数(duration/easing/loop 等)”分清
    • 不确定的配置键,不要让它误入“可动画属性”的判定
    • 建议将“非动画属性”的扩展项放进 params.options(现有实现已倾向此策略)

4.5 兼容性/健壮性

  • 代码应能在“没有 bundler”的脚本环境运行
  • 避免依赖现代语法到不可控程度(如确需使用,确保目标环境可用)
  • 对 DOM/Window 等对象引用前做存在性判断(SSR/非浏览器环境不会直接崩溃)

5. CSS 规范(xjs.css

5.1 单文件策略

  • 所有库运行时 UI 样式集中在 xjs.css
  • JS 侧不要注入大量 <style>(可接受“确保已引入 CSS”的兜底逻辑,但不把完整样式写入 JS)

5.2 命名空间与前缀(避免污染业务)

目前 Layer 使用 .layer-*,后续新增组件建议遵循:

  • 组件前缀x-<component>-* 或沿用当前 layer-*(但需保持一致)
  • 推荐做法:统一前缀策略(后续若决定全库统一 xjs-,则逐步迁移)

目标是:业务 CSS 不会因为类名冲突而被库“意外影响”,也不会轻易覆盖库样式。

5.3 主题化(建议方向)

新增 UI 组件时,尽量通过 CSS 变量暴露可定制项:

  • --xjs-color-primary
  • --xjs-radius-md
  • --xjs-shadow-lg
  • --layer-...(若继续沿用 layer 命名空间)

并保证:

  • 默认值可用
  • 业务覆盖变量即可换肤(不需要改库内部选择器)

6. 文档与测试(doc/

6.1 入口约定

  • 所有测试页面统一从 doc/index.html 进入
  • 新增测试页面时:
    • 放到合适的子目录(例如 doc/layer/doc/svg/doc/tween_*
    • doc/index.html 的导航树中挂入口,保证“可点击直达”

6.2 测试页面规范(建议)

  • 每个测试页只验证一类能力(避免“一页测所有导致回归困难”)
  • 页面顶部写清楚:
    • 测试目标
    • 预期现象
    • 参数组合/边界条件

6.3 功能演示页面约定(必须可精确定位)

目标:我只说“改哪个功能的演示”,你能精准找到对应页面、路由和引用的库。

  • 入口路由(doc 首页)/doc/#<token> → 映射到 doc/<path>.html
    • 例:/doc/#layer_test__dom__stepsdoc/layer/test_dom_steps.html
  • 示例页面结构
    • 演示区块在上、代码区块在下(tabs + code),便于直接看效果
    • 演示区块应包含最小可操作按钮/交互
  • 代码展示必须一致
    • HTML/CSS/JS 的代码块必须与实际页面/示例一致
    • 禁止使用 ...、伪代码或省略写法(如隐藏 DOM 块也要完整展示)
  • 必须显式声明引用的库(页面内)
    • 样式:doc/demo.css(示例通用样式)、xjs.css(组件运行时样式)
    • 脚本:doc/highlight_css.js(代码高亮)
    • 功能库:animal.js + layer.js(调试时直接加载源码,避免缓存覆盖)

落地示例(当前对照页):

  • 功能:Layer DOM + Steps
  • 路由:/doc/#layer_test__dom__steps
  • 页面:doc/layer/test_dom_steps.html

7. 扩展开发指南(新增能力的推荐落点)

7.1 新增动画能力(优先落在 animal.js

典型落点:

  • 新增 Selection 方法:Selection.prototype.xxx = function (...) { ... }
  • 或在 Selection 类体内增加方法(更推荐,保持结构统一)
  • 如需新增静态能力:挂在 $.xxx = ...xjs/animal 为别名)

7.2 新增 UI 组件(独立文件 + CSS )

推荐方式:

  • 新增 component_xxx.js(或未来统一 components/xxx.js
  • xjs.css 增加 xxx-*(或 xjs-xxx-*)样式
  • 在构建阶段把该模块纳入单文件合并(更新 main.go build() 的拼接顺序)

当前 main.go 固定合并 animal.js + layer.js,当模块增长时建议升级为“配置式列表”。


8. 发布与回归清单(面向生产)

每次准备对外发布(或业务项目更新版本)前建议走一遍:

  • 功能回归
    • 打开 doc/index.html,对本次变更相关页面逐个验证
  • 构建产物
    • 执行 go run main.go build
    • 确认 dist/xjs.js 更新(不要手改)
  • 对外 API 稳定性
    • xjs/animal/Layer 是否都存在
    • $ 是否仍然是“显式开关才导出”
  • 样式影响面
    • xjs.css 的类名是否避免与业务冲突
    • 是否通过 CSS 变量提供可配置项(可选但推荐)

9. 建议的后续演进(非强制,但推荐)

当模块越来越多时,建议逐步引入以下工程化能力(仍保持“最终只发两个文件”的目标):

  • 源码目录化:例如 src/animal/src/layer/src/core/
  • 构建脚本升级main.go 从“硬编码拼接两个文件”升级为“按清单拼接多个文件”
  • 产物头信息:在 dist/xjs.js 注入版本号、构建时间、变更摘要(便于定位线上问题)
  • 最小化/压缩(可选):如需压缩,保持可回溯到未压缩构建(source map 视需求)

10. 约定优先级

如果本文档与源码现状出现不一致:

  1. 以本文档为准(作为未来统一标准)
  2. 在不破坏对外 API 的前提下,逐步把历史实现迁移到本文档规范

11. Layer 新增能力:DOM 内容 / Steps(分步弹窗)

11.0 最新使用入口(推荐)

  • 主入口$(同时保留 xjs/animal 兼容别名)
  • 静态弹窗Layer.run(options)$.run(options)
  • 链式弹窗$.layer(options).step(...).run()
  • 事件绑定$('#btn').click(function () { /* this 为按钮 */ })

示例(点击触发):

$('#openDialog').click(function () {
  $.layer({
    title: 'Hello',
    text: 'Triggered by click',
    showCancelButton: true
  });
});

11.1 DOM 内容(显示指定 DOM,而不是一句话)

Layer 现在支持把“页面里已有的 DOM(可默认隐藏)”挂载到弹窗内容区域展示:

  • 推荐参数dom: '#selector' | Element | <template>
  • 兼容参数content: '#selector' | Element(历史用法仍可用)
  • 行为
    • 默认模式为 move:把 DOM 节点移动进弹窗,关闭时会 自动还原到原位置(同时还原 hidden / style.display
    • 可选 clone:通过 domMode:'clone'content:{ dom:'#el', clone:true } 克隆节点展示(不移动原节点)

示例:

// 页面中一个默认隐藏的 DOM
// <div id="my_form" style="display:none">...</div>

$('#openForm').click(function () {
  $.layer({
    title: 'Edit profile',
    dom: '#my_form',
    showCancelButton: true,
    confirmButtonText: 'Save'
  });
});

11.2 Steps(多个对话框 next 平滑转化 / 分步填写)

Layer 支持“同一弹窗内”的步骤流:点击确认按钮不关闭,而是平滑切换到下一步;最后一步确认后 resolve。

演示:/#layer_test__dom__steps

推荐写法(链式):

$('#openWizard').click(function () {
  $.layer({
    icon: 'info',
    closeOnClickOutside: false,
    closeOnEsc: false
  })
  .step({
    title: 'Step 1',
    dom: '#step1',
    showCancelButton: true,
    cancelButtonText: 'Cancel',
    preConfirm(popup) {
      // return false 阻止进入下一步(用于校验)
      const v = popup.querySelector('input[name="name"]').value.trim();
      if (!v) return false;
      return { name: v };
    }
  })
  .step({
    title: 'Step 2',
    dom: '#step2',
    preConfirm(popup) {
      return { plan: popup.querySelector('select[name="plan"]').value };
    }
  })
  .run()
  .then((res) => {
    if (res.isConfirmed) {
      // res.value 是每一步 preConfirm 的返回值数组
      console.log(res.value);
    }
  });
});

更简单的“容器式 steps”写法(适合表单收集):

$('#step').click(function () {
  $.layer({
    step: '#stepContainer',      // 容器
    stepItem: '.stepItem',       // 可选:没有则默认取容器的一级子元素
    title: 'Create order',       // 其他 Layer 选项仍可用(作为 base)
    showCancelButton: true
  }).then((res) => {
    if (res.isConfirmed) {
      // res.data 为统一汇总的表单数据(来自 steps 内所有 input/select/textarea)
      console.log(res.data);
    }
  });
});

补充说明:

  • stepItem 不传时,会自动用 #stepContainer 的一级子元素作为每一步
  • 单步标题可由每个 step DOM 自身提供:data-step-title / data-layer-title / title

也支持便捷写法:

Layer.flow([
  { title: 'Step 1', dom: '#step1' },
  { title: 'Step 2', dom: '#step2' }
], { icon: 'info' });