@johnmorrisdotca/kazoku-ui

中文文档
0.2.1发布于 2026/11/01MITTypeScriptES Module零依赖构建来源已验证

Kazoku UI 家族, the family's shared interface parts as their own versioned package. First part: view controls for any 3D or zoomable view (angle, tilt, auto-turn, zoom about the pointer, pan, touch, address state) as a framework-free custom element and a pl

原始使用文档
文档目录13 节
  1. View controls
  2. In plain HTML
  3. In a framework
  4. Without the element
  5. Buttons and links inside the view
  6. Opening at the scene's natural size
  7. The event API
  8. Attributes
  9. The address
  10. Why the tag is only defined when it is free
  11. Size
  12. Checks
  13. Not yet

Kazoku UI 家族

The demo: a white card with a blue circle and a black triangle, turned a little and tipped, in a frame with a dotted floor. Under it the view controls: Angle and Tilt sliders, Auto-turn and Front buttons, zoom out, 2 times, zoom in and Fit, four arrows to move the zoomed view, and the Move and Turn choice for a drag.

The family's shared interface parts, as their own versioned package: one part per subpath, so a page loads only what it uses. No runtime dependencies, MIT. The first part is the view controls for any 3D or zoomable view. They never draw the scene: they tell your page what the reader chose, and your canvas, WebGL, SVG or CSS 3D applies it.

Demo · Labels, English and 日本語 · Changelog

npm install @johnmorrisdotca/kazoku-ui

Parts (each its own subpath; later ones: picture-tile pickers, theme swatches with light and dark, the English and 日本語 switch, Help, GitHub and npm pills, Save SVG and PNG, the family's backgrounds):

SubpathWhat it is
@johnmorrisdotca/kazoku-ui/view-controlsThe controller, the arithmetic, the address and the labels. Imports on a server.
@johnmorrisdotca/kazoku-ui/view-controls/elementThe ViewControlsElement class (needs a browser).
@johnmorrisdotca/kazoku-ui/view-controls/defineRegisters <kazoku-view-controls> (once, and only if the tag is free).

View controls

  • Angle and tilt sliders, Auto-turn with play and pause, Front.
  • Zoom out, 2 times, zoom in and Fit. Zoom is about the pointer (and Ctrl or Command with the wheel, or a trackpad's pinch).
  • The four-arrow pan pad (shown once zoomed in) and the Move or Turn choice for what a drag does.
  • Touch: one finger turns at 1x and moves the view when zoomed in; two fingers pinch.
  • Keys on the view: arrows turn and tip it, + and - zoom, 0 or Home is Front and Fit.
  • The choices are kept in the address, with keys you can rename.
  • Labels in English and Japanese; light and dark; 48 px touch targets; a compact bar on a phone; no automatic turn for a reader who prefers reduced motion.
  • A control never stops or resets the turn. Zoom, tilt, pan, the Move or Turn choice, Fit and Front all leave Auto-turn running (Front only sets the angle to 0 and the turn goes on from there). A hand on the angle slider, or turning the view by dragging it, holds the turn still while it is down and the turn carries on from where the hand left it. Only the Auto-turn button pauses it.

In plain HTML

<div id="scene" tabindex="0" style="height: 320px">...your scene...</div>
<kazoku-view-controls for="scene" address></kazoku-view-controls>

<script type="module">
  import "@johnmorrisdotca/kazoku-ui/view-controls/define";

  const scene = document.getElementById("scene");
  document.querySelector("kazoku-view-controls").addEventListener("kazoku-viewchange", (event) => {
    const { yaw, tilt, zoom, panX, panY } = event.detail;
    scene.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom}) rotateX(${tilt}deg) rotateY(${yaw}deg)`;
  });
</script>

Put the element on the view or right beside it, so it is on screen with the view. for names the element the hands bind to (the pointer, pinch, wheel and keys); give it a tabindex if you want the keys on it, and size it to the view, because the pan is held to the room the zoom gives. With overlay the bar sits over the bottom of its positioned parent instead.

In a framework

The element is plain DOM, so any framework uses it with a ref and one listener. React:

import "@johnmorrisdotca/kazoku-ui/view-controls/define";
import { useEffect, useRef } from "react";

export function Viewer({ draw }) {
  const view = useRef(null);
  const controls = useRef(null);
  useEffect(() => {
    const bar = controls.current;
    bar.view = view.current;
    const on = (event) => draw(event.detail);
    bar.addEventListener("kazoku-viewchange", on);
    return () => bar.removeEventListener("kazoku-viewchange", on);
  }, [draw]);
  return (
    <>
      <div ref={view} tabIndex={0} style={{ height: 320 }} />
      <kazoku-view-controls ref={controls} lang="en" />
    </>
  );
}

Without the element

createViewController is the same hands and state with no markup of its own, for a page that draws its own buttons. It touches the DOM only in bind.

import { createViewController } from "@johnmorrisdotca/kazoku-ui/view-controls";

const view = createViewController({
  speed: 30, // degrees a second for Auto-turn
  onChange(state, { final, reason }) {
    draw(state); // { yaw, tilt, zoom, panX, panY, turning, drag }
  },
});
const unbind = view.bind(document.getElementById("scene")); // pointer, pinch, wheel, keys
myPlayButton.onclick = () => view.toggle();
myZoomIn.onclick = () => view.zoomBy(1.4);

Options of createViewController that match: zoom ("fit" or a number), fitScale, ignore. The controller also has ignore (changeable), fitScale, startZoom (the zoom the view opens at) and setFitScale(n), setStartZoom(zoom); DEFAULT_IGNORE is the default selector.

The hands capture the pointer on the for element, so a press on a button or link placed inside it would be taken for a drag and its click lost. A press on anything that matches ignore is left alone (no capture, no drag, no double-tap), and the click goes through. Mark any other child with data-kazoku-ignore. Dragging from the empty part of the view turns it as before.

ignore also sees through shadow DOM: it tests every element on the event's path from the innermost one up to the view, so a button inside a web component's open shadow root (a card's own Animate button, say) is left alone even though the event's target is the component. A closed shadow root hides its inside from the page, so only the component itself can be matched: mark the host with data-kazoku-ignore, or add its tag name to ignore.

Opening at the scene's natural size

Zoom 1 is Fit, whatever size that is on the page. A page that draws its scene at fitScale (for example 0.4 of natural size when it fits the view) and wants to open at natural size sets zoom="1" and tells the controls the scale:

const controls = document.querySelector("kazoku-view-controls");
controls.setAttribute("zoom", "1"); // natural size
controls.setFitScale(measure()); // the scale the scene is drawn at when it fits, for example 0.4
// the event's zoom is now 2.5; draw at event.detail.zoom * controls.fitScale

setFitScale moves the view to the opening zoom for the new scale, unless the reader has already zoomed. Fit and the Fit key still go to zoom 1, and the address leaves a zoom out when it is the opening one. zoom="fit", or no zoom, changes nothing from before.

The event API

kazoku-viewchange is dispatched on the element (it bubbles and crosses shadow roots). event.detail, and the first argument of onChange, is:

FieldMeaning
yawDegrees, -180 up to 180: the turn about the vertical. Dragging right makes it larger.
tiltDegrees, held to plus or minus tilt-max (30): the top tips away as it gets larger (dragging up).
zoom1 to 5 (zoom-min, zoom-max): 1 is Fit. The scene is drawn at zoom * fitScale (see below).
panX, panYCSS pixels the zoomed picture is moved from the middle of the view; 0 at 1x.
turningTrue while Auto-turn is running.
drag"move" or "turn": what a plain drag does while zoomed in.
finalFalse for the steps of a drag, a pinch, a slider and the automatic turn; true when something settled. Do the costly drawing on final, the cheap transform on every event.
reasonWhat changed it: init, auto, drag, pinch, wheel, key, slider, zoom, pan, front, fit, fit-scale, play, pause or set.

An init event is sent once, a moment after the element connects, with the starting view (from the attributes and the address). element.state is the current view at any time.

Attributes

Attribute
forThe id of the view the hands bind to. Or set element.view = someElement.
langen or ja. Otherwise the nearest lang above it, else English.
yaw, tiltThe starting angle and tilt.
zoomThe starting zoom: fit (the default) or a number, a scale where 1 is the scene's natural size. It opens at that number divided by fit-scale; with fit-scale left at 1 it is the zoom of the event.
fit-scaleThe scale your page draws the scene at when it is at Fit (event zoom 1): what zoom 1 means. 1 by default. Or call element.setFitScale(n) once the scene is measured.
ignoreA CSS selector for what inside the view a pointer press leaves alone, so its click goes through: button, a, input, select, textarea, label, [data-kazoku-ignore] by default. A bare ignore="" leaves nothing alone.
autoplayTurn by itself from the start, unless the reader prefers reduced motion.
speedDegrees a second (30).
flatThe view cannot turn: no angle, tilt, Auto-turn or Front, and a drag moves it.
tilt-max, zoom-min, zoom-maxThe limits (30, 1, 5).
addressKeep the choices in the page's address (?yaw=40&tilt=10&zoom=2&drag=turn).
address-keysOther keys: yaw:a,zoom:z.
themelight or dark; otherwise the page's colour scheme.
overlaySit over the bottom of a positioned parent.

Methods: play(), pause(), front(), fit(), set({ yaw, tilt, zoom, panX, panY, drag }), setFitScale(n), and controller (the createViewController object behind it). element.words = { front: "Home" } replaces labels. Colours are custom properties: --kazoku-ink, --kazoku-surface, --kazoku-line, --kazoku-accent; the parts are bar, field, slider and button.

The address

Only what a reader chose is kept: angle, tilt, zoom and the drag choice. The pan is in pixels of one screen and the turning is of a moment, so neither is; while the view turns by itself the angle is left out. A value at its start (Fit, or the zoom the page opens at) leaves the address, and other parameters are kept. The address is written at most four times a second (Safari stops a page that changes its address more than 100 times in 30 seconds). Without the element: parseAddress(search, keys?) and formatAddress(search, choices, keys?, leaveYaw?, startZoom?).

Why the tag is only defined when it is free

customElements.define throws when a name is taken, and a page can meet that without any fault: two copies of this package (an app and a widget that each bundle one, or two versions), the script loaded twice, or a page that registered the tag to supply its own element. A throw would stop the importing module and the page's script with it. So define calls customElements.get first and the first definition wins; defineViewControls(registry?) returns whether it defined the tag.

Size

Gzipped, unbundled: the element and its definition about 5.0 KB, the controller 3.9 KB, the maths 1.4 KB, the address 1.2 KB, the labels 0.7 KB and the styles 1.6 KB (pnpm test:package prints them). Nothing is loaded from the network.

Checks

pnpm check (lint, types, 29 unit tests of the maths, the address, the controller and the package, the build and the packed package), and pnpm test:demo (the demo in Chromium and WebKit at a phone's width and a desk's: the controls in the first screen, a toggle during Auto-turn leaves the angle advancing, a button or link inside the view still clicks while a drag turns it, the opening zoom and fit scale, zoom about the pointer, pan when zoomed, touch, the address, the labels, reduced motion).

Not yet

Not published to npm yet. The Japanese labels have not yet been read by a native speaker. The demo's scene is a CSS 3D card; the package has no opinion about what draws.

Licence: MIT. No brand, league or sponsor mark is in this package.

版本与发布记录

@johnmorrisdotca/kazoku-ui 共发布 4 个版本,首次发布于 2026/11/01;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.1latest2026/11/012 天前
0.2.02026/11/012 天前
0.1.02026/11/012 天前
0.0.0-stage2026/11/012 天前

关于 @johnmorrisdotca/kazoku-ui 的常见问题

@johnmorrisdotca/kazoku-ui 是什么?

家族的共享接口部件作为自己的版本化包。第一部分:任何3D或可缩放视图的视图控件(角度,倾斜,自动旋转,缩放指针,平移,触摸,地址状态)作为无框架自定义元素和pl

如何安装 @johnmorrisdotca/kazoku-ui?

在项目目录运行 npm install @johnmorrisdotca/kazoku-ui,或使用 pnpm add / yarn add / bun add。

@johnmorrisdotca/kazoku-ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@johnmorrisdotca/kazoku-ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@johnmorrisdotca/kazoku-ui 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@johnmorrisdotca/kazoku-ui 可以商用吗?

@johnmorrisdotca/kazoku-ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@johnmorrisdotca/kazoku-ui 还在维护吗?

最新版本 0.2.1 发布于 2026/11/01(2 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/11/01。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。