# ray-visutil

> vis utils

Latest version **1.3.21** (published 2026-04-23) · MIT license · 0 weekly downloads

## Install

```sh
npm install ray-visutil
pnpm add ray-visutil
yarn add ray-visutil
bun add ray-visutil
```

## Health

**Score 55/100 (C)** — status: stable.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support.

## Facts

| | |
|---|---|
| Version | 1.3.21 |
| Published | 2026-04-23 |
| First published | 2018-07-14 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 0 |
| Unpacked size | 427 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | ilex.h |
| Maintainers | ilex.h |
| Keywords | vis, util |

## Links

- npm: https://www.npmjs.com/package/ray-visutil
- npm.io page: https://npm.io/package/ray-visutil

## Alternatives

- [lodash.assign](https://npm.io/package/lodash.assign.md) — 2.3M weekly downloads
- [lodash.chunk](https://npm.io/package/lodash.chunk.md) — 1.8M weekly downloads
- [react-native-ios-utilities](https://npm.io/package/react-native-ios-utilities.md) — 138.5K weekly downloads
- [@technically/lodash](https://npm.io/package/@technically/lodash.md) — 50.9K weekly downloads
- [@fluid-topics/ft-icon](https://npm.io/package/@fluid-topics/ft-icon.md) — 20.6K weekly downloads

## Recent versions

- 1.3.21 (latest) — 2026-04-23
- 1.3.20 — 2024-09-26
- 1.3.19 — 2024-05-13
- 1.3.18 — 2024-04-30
- 1.3.17 — 2024-04-30
- 1.3.16 — 2024-04-03
- 1.3.15 — 2024-04-03
- 1.3.14 — 2024-02-23
- 1.3.13 — 2024-01-15
- 1.3.12 — 2023-12-26
- 1.3.11 — 2023-11-14
- 1.3.10 — 2022-08-24
- 1.3.9 — 2022-04-06
- 1.3.8 — 2022-01-11
- 1.3.7 — 2021-12-22
- … 40 more at https://npm.io/package/ray-visutil/versions

## README

# ray-visutil

vis tools

## author

ilex.h

## methods

* ColorUtils

* SvgUtils

* fullscreen

* memorize 缓存方法执行结果

* deepEqual

* shallowEqual

* resizeEvent [bindResize, unbindResize]

* createInterface

```js
const MyInterface = Interface.create('myMethodA', 'myMethodB')
// or
const MyInterface = new Interface('myMethodA', 'myMethodB')

class MyClass extends MyInterface {
  constructor () {
    super()
  }

  myMethodA () {
    // ...implementation goes here
  }
}

const instance = new MyClass()
// throws a new error with the message:
// 'The following function(s) need to be implemented for class MyClass: myMethodB'

class MySubClass extends MyClass {
  constructor () {
    super()
  }

  myMethodA () {
    // override 'myMethodA'
  }
}

const instance = new MySubClass()
// still throws an error with the message:
// 'The following function(s) need to be implemented for class MyClass: myMethodB'
```

isImplementedBy

```js
const MyInterface = new Interface('myMethod')

class MyClass {
  myMethod () {
    // some implementation
  }
}

class MyOtherClass {
  myOtherMethod () {
    // some implementation
  }
}

const instanceA = new MyClass()
const instanceB = new MyOtherClass()

MyInterface.isImplementedBy(instanceA) // returns true
MyInterface.isImplementedBy(instanceB) // returns false
```

## transition

过渡动画

移植于 [@tweenjs/tween.js](https://www.npmjs.com/package/@tweenjs/tween.js), or [tween.js](https://github.com/tweenjs/tween.js)

用于简单动画的JavaScript补间引擎，结合了优化的 Robert Penner 方程。

### example

```js
import TG from 'ray-visutil/lib/transition';

function render(){
  const box = document.createElement('div');
  box.style.setProperty('background-color', '#008800');
  box.style.setProperty('width', '100px');
  box.style.setProperty('height', '100px');
  document.body.appendChild(box);

  // Setup the animation loop.
  function animate(time) {
      requestAnimationFrame(animate);
      TG.update(time);
  }
  requestAnimationFrame(animate);

  const coords = { x: 0, y: 0 }; // 起始点 (0, 0)
  const transition = new TG.Transition(coords) // 创建一个新的transition用来改变 'coords'
          .to({ x: 300, y: 200 }, 1000) // 在1s内移动至 (300, 200)
          .easing(TG.Easing.Quadratic.Out) // 使用缓动功能使的动画更加平滑
          .onUpdate(function() { // Called after TG updates 'coords'.
              // 将 'box' 移动到 'coords' 所描述的位置，配合 CSS 过渡
              box.style.setProperty('transform', 'translate(' + coords.x + 'px, ' + coords.y + 'px)');
          })
          .start(); // Start the transition immediately.
}

render();

```

### options

#### TransitionGroup

`import TG from 'ray-visutil/lib/transition/TransitionGroup';`

用于 添加、删除、更新动画

* getAll()
* removeAll()
* add(transition)
* remove(transition)
* update(time, preserve)

#### TransitionCore

`import TG from 'ray-visutil/lib/transition/TransitionCore';`

动画核心库，用于实现动画

* constructor(object, group) : `object 为初始属性`
* getId()： `获取动画id`
* isPlaying(): `判断是否正在执行动画`
* to(properties, duration): `从初属性转化为目标属性，duration 为耗时时间，默认 1000`
* start(time)： `开启动画`
* stop()： `停止动画`
* end()： `终止动画，设置 update(time)中的，time = startTime + duration`
* stopChainedTransitions()：`停止 chain 动画`
* group()： `重新设置 TG`
* delay(amount)： `延时时间`
* repeat(times)： `重复执行次数`
* repeatDelay(amount): `设置重复延时`
* kickBack(kb): `设置回弹`
* easing(eas)： `设置动画速度函数，默认为 [Easing.Linear.None]`
* interpolation(inter): `插入动画，默认为 [Interpolation.Linear]`
* chain(): `用于多个动画之间连续交换`
* onStart(callback)： `start 时回调`
* onUpdate(callback): `更新时回调，回调函数中的参数为constructor中具体的 object 值`
* onComplete(callback)： `动画执行完成时的回调，回调函数中的参数为constructor中具体的 object 值`
* onStop(callback)： `设置停止动画时的回调，回调函数中的参数为constructor中具体的 object 值`
* update(time)： `更新动画`

#### TG

`import TG from 'ray-visutil/lib/transition';`

实质是一个 `TransitionGroup` 实例。

* TG.TransitionGroup
* TG.Transition
* TG.Easing
* TG.Interpolation

### polylabel

Given polygon coordinates in
[GeoJSON-like format](http://geojson.org/geojson-spec.html#polygon)
and precision (`1.0` by default),
Polylabel returns the pole of inaccessibility coordinate in `[x, y]` format.

* 基本使用

```js
import polylabel from 'ray-visutil/lib/polylabel';

const polygon = [
  [[3116, 3071], [3118, 3068], [3108, 3102], [3100, 3105]],
  [[4016, 1878], [4016, 1864], [4029, 1859], [4024, 1850], [4008, 1839]],
  [([3315, 1339], [3327, 1332], [3331, 1324], [3323, 1329])]
];

const p = polylabel(polygon, 1.0); // [3106.1875, 3098.9375]
const p = polylabel(polygon, 50); // [3108.8702290076335,3090.8778625954196]
```

* test

```js
var p = polylabel([[[0, 0], [1, 0], [2, 0], [0, 0]]]); // [0, 0]

p = polylabel([[[0, 0], [1, 0], [1, 1], [1, 0], [0, 0]]]); // [0, 0]
```

### TinyQueue

* 基本使用

```js
import TinyQueue from 'ray-visutil/lib/polylabel/TinyQueue';
// create an empty priority queue
var queue = new TinyQueue();

// add some items
queue.push(7);
queue.push(5);
queue.push(10);

// remove the top item
var top = queue.pop(); // returns 7

// return the top item (without removal)
top = queue.peep(); // returns 7

// get queue length
queue.length; // returns 3

// create a priority queue from an existing array (modifies the array)
queue = new TinyQueue([7, 5, 10]);

// pass a custom item comparator as a second argument
queue = new TinyQueue([{value: 5}, {value: 7}], (a, b) => a.value - b.value);

// turn a queue into a sorted array
var array = [];
while (queue.length) {
  array.push(queue.pop());
}
```

* 性能测试

```js
import TinyQueue from 'ray-visutil/lib/polylabel/TinyQueue';

const N = 1000000;

const data = [];
for (let i = 0; i < N; i++) data[i] = {value: Math.random()};

const q = new TinyQueue(null, compare);

function compare(a, b) {
    return a.value - b.value;
}

console.time(`push ${N}`);
for (let i = 0; i < 1000000; i++) q.push(data[i]);
console.timeEnd(`push ${N}`);

console.time(`pop ${N}`);
for (let i = 0; i < 1000000; i++) q.pop();
console.timeEnd(`pop ${N}`);
```

### Store

since 2019-01-14 v1.0.10

indexedDB 使用

还可以使用另一个比较好的开源库 [localforage](https://github.com/localForage/localForage)

[中文文档库](https://localforage.docschina.org/)

```js
import Store from 'ray-visutil/lib/Store';

const store = new Store({ name: 'ray-3dobj', storeName: 'models' });

store.init(() => {

});

const data = {};

store.set(data, () => {});
store.get((data) => {

});
store.clear();
```

### tools

* isArray(val: any): boolean;
* isObject(val: any): boolean;
* isString(val: any): boolean;
* isNumber(val: any): boolean;
* isFunction(val: any): boolean;
* isRegExp(val: any): boolean;
* isDom(val: any): boolean;
* isNull(val: any): boolean; **判断 undefined 和 null**
* isBlank(val: any): boolean; **判断空白**
* isEmptyObj(val: any): boolean;
* isEmptyArray(val: any): boolean;
* clone(o: any): any; **克隆数据, 采用 JSON.parse 实现**
* merge(target: any, source: any, overwrite: boolean): any;
* hasSameProperties(o1: Object, o2: Object): boolean;
* parseValue(o: any, defaultValue: any): any; **解析数据，当 o 为空时，则取默认值，反之则是 o**
* omit(obj: Object, fields: String | String[]): Object; **删除不需要的属性**

```js
import { tools } from 'ray-visutil';

// or import tools from 'ray-visutil/lib/tools';
```

### str2html

将 string 转化为 html

```js
import str2html from 'ray-visutil/lib/str2html';

var ss = str2html('<li>aaaa</li>');
var ss2 = str2html('<li>aaaa</li><li>aaaa</li><li>aaaa</li>');
var ss3 = str2html('<ul><li>aaaa</li><li>aaaa</li><li>aaaa</li></ul>');

// [li]
// [li, li, li]
// [ul]

```

### Pivot

@since 1.1.0

#### Pivot basic use

```js
import Pivot from 'ray-visutil/lib/pivot';

//custom object that dispatch a `started` pivot
const myObject = {
  changed : new Pivot(),
  ended : new Pivot()
};
function onChange(param1, param2){
  alert(param1 + param2);
}

myObject.changed.add(onStarted); //add listener
myObject.changed.dispatch('foo', 'bar'); //dispatch pivot passing custom parameters
myObject.changed.remove(onStarted); //remove a single listener
```

#### Pivot Multiple Listeners

```js
function onStop(){
  alert('ended');
}

function onStop2(){
  alert('ended listener 2');
}

myObject.ended.add(onStop);
myObject.ended.add(onStop2);
myObject.ended.dispatch();
myObject.ended.removeAll(); //remove all listeners of the `ended` pivot
```

#### Stop/Halt Propagation (method 1)

```js
myObject.changed.add(function(){
  myObject.changed.halt(); //prevent next listeners on the queue from being executed
  // return false; //if handler returns `false` will also stop propagation
});
myObject.changed.add(function(){
  alert('second listener'); //won't be called since first listener stops propagation
});
myObject.changed.dispatch();
```

#### Set execution context of the listener handler

```js
var foo = 'bar';
var obj = {
  foo : 10
};

function handler1(){
  alert(this.foo);
}
function handler2(){
  alert(this.foo);
}
//note that you cannot add the same handler twice to the same pivot without removing it first
myObject.changed.add(handler1); //default execution context
myObject.changed.add(handler2, obj); //set a different execution context
myObject.changed.dispatch(); //first handler will alert "bar", second will alert "10".
```

#### Set listener priority/order

```js
var handler1 = function(){
  alert('foo');
};
var handler2 = function(){
  alert('bar');
};
myObject.changed.add(handler1); //default priority is 0
myObject.changed.add(handler2, null, 2); //setting priority to 2 will make `handler2` execute before `handler1`
myObject.changed.dispatch(); //will alert "bar" than "foo"
```

### GIF

@since v1.1.1

```js
import GIF from 'ray-visutil/lib/gif';

function loadGIF(url, renderGIF){
  const oReq = new window.XMLHttpRequest();
  oReq.open('GET', url, true);
  oReq.responseType = 'arraybuffer';

  oReq.onload = function(oEvent) {
    var arrayBuffer = oReq.response; // Note: not oReq.responseText
    if (arrayBuffer) {
      gif = new GIF(arrayBuffer);
      var frames = gif.decompressFrames(true);
      console.log(gif);
      // render the gif
      renderGIF(frames);
    }
  };
  oReq.send(null);
}

let loadedFrames;
let frameIndex;

loadGIF('/example/res/car.gif', frames => {
  loadedFrames = frames;
  // user canvas
  var c = document.getElementById('mycanvas');
  var ctx = c.getContext('2d');
  // gif patch canvas
  var tempCanvas = document.createElement('canvas');
  var tempCtx = tempCanvas.getContext('2d');
  // full gif canvas
  var gifCanvas = document.createElement('canvas');
  var gifCtx = gifCanvas.getContext('2d');

  c.width = frames[0].dims.width;
  c.height = frames[0].dims.height;

  gifCanvas.width = c.width;
  gifCanvas.height = c.height;

  renderFrame();
});

function renderFrame() {
  // get the frame
  var frame = loadedFrames[frameIndex];

  var start = new Date().getTime();

  gifCtx.clearRect(0, 0, c.width, c.height);

  // draw the patch
  drawPatch(frame);

  // perform manipulation
  manipulate();

  // update the frame index
  frameIndex++;
  if (frameIndex >= loadedFrames.length) {
    frameIndex = 0;
  }

  var end = new Date().getTime();
  var diff = end - start;

  if (playing) {
    // delay the next gif frame
    setTimeout(() => {
      requestAnimationFrame(renderFrame);
      //renderFrame();
    }, Math.max(0, Math.floor(frame.delay - diff)));
  }
}

function drawPatch(frame) {
  var dims = frame.dims;

  if (!frameImageData || dims.width !== frameImageData.width || dims.height !== frameImageData.height) {
    tempCanvas.width = dims.width;
    tempCanvas.height = dims.height;
    frameImageData = tempCtx.createImageData(dims.width, dims.height);
  }

  // set the patch data as an override
  frameImageData.data.set(frame.patch);

  // draw the patch back over the canvas
  tempCtx.putImageData(frameImageData, 0, 0);

  gifCtx.drawImage(tempCanvas, dims.left, dims.top);
}

```

### ImageProcessor

@since v1.1.2

图片处理 `颜色校正、高斯模糊`

`createInstance`: 创建 canvas instance，包含核心方法或属性： `texture, draw, update, replace, contents, getPixelArray`

挂载对象 `$_$: { gl, isInitialized, texture, spareTexture, flippedShader }`

#### 使用完整版 processor

```js
import imgProcessor from 'ray-visutil/lib/imageProcessor/processorAll';

function createImg(){
  const img = new window.Image(300, 300);
  img.src = '/example/res/landscape.jpg';
  img.onload = imgOnload;
  return img;
}

function imgOnload(){
  const canvas = imgProcessor.canvas();
  const texture = canvas.texture(this);
  canvas.draw(texture).triangleBlur(50).update();

  // replace the image with the canvas
  document.body.appendChild(canvas);
}

createImg();
```

#### 使用自定义滤镜

```js
import { createInstance, wrap } from 'ray-visutil/lib/imageProcessor';

import triangleBlur from 'ray-visutil/lib/imageProcessor/filters/blur/triangleblur';

const imgProcessor = {
  canvas(){
    const canvas = createInstance();
    canvas.triangleBlur = wrap(triangleBlur);
    // 全局查看
    window.$$canvas = canvas;
    return canvas;
  }
};

// 使用 imgProcessor
```

### screenLog

显示 log 内容。会将 console 日志打印在界面上

```js
import screenLog from 'ray-visutil/screenLog';

// 初始化
screenLog.init('screen-log', { autoScroll: false });

screenLog.log('String: Hello world');
screenLog.log(`Numbers: ${124}`);
screenLog.log(21, 'multiple arguments');
screenLog.log('Arrays', [1, 2, 3]);
screenLog.log('Objects', { a: 3 });

console.log('console.log also gets logged.');

var i = 10;
function log() {
  console.log('Future log', Date.now());
  console.error('Future error', Date.now());
  console.warn('Future warn', Date.now());
  console.info('Future info', Date.now());
  if (--i) {
    setTimeout(log, 1000);
  }
}
log();

```

#### screenlog options 说明

* `init(parent, options)` parent: 需要绑定的父节点，可以是 id选择器、或者具体的 DOM。

```js
// 默认 options
const options = {
  bgColor: '#232323',
  logColor: 'lightgreen',
  infoColor: 'blue',
  warnColor: 'orange',
  errorColor: 'red',
  fontSize: '10px',
  freeConsole: false,
  css: `
    z-index:99;
    font-family:Helvetica,Arial,sans-serif;
    padding:5px;
    text-align:left;
    opacity:0.8;
    position:absolute;
    bottom:0;
    width:100%;
    height:100%;
    font-weight:bold;
    overflow:auto;
    box-sizing: border-box;
  `,
  autoScroll: true
};
```

* `log(...args)` 通用日志，类似于 `console.log`
* `info(...args)` 信息类型，类似于 `console.info`
* `warn(...args)` 警告类型，类似于 `console.warn`
* `error(...args)` 错误类型，类似于 `console.error`
* `clear()` 清空日志
* `destroy()` 销毁

### Heatmap

since@1.1.4

热力图

#### 直接使用 Heatmap

```js
import Heatmap from 'ray-visutil/lib/heatmap';

const hm = new Heatmap({
  mapSize: 256,
  width: 12, // 宽度 单位米
  height: 8, // 长度 单位米
  minValue: 10,
  maxValue: 25,
  radius: 1, // 单个点的热力影响半径
  transparent: false // 未插值区域是否透明（默认为 false ）
});

// 更新热图数值
setInterval(() => {
  // 数据格式为
  // [x坐标,y坐标,热力值]
  // 坐标系以热力图平面中心为原点
  hm.randomData();
}, 1000);


// document.body.appendChild(hm.getCanvas());
document.body.appendChild(hm.canvas);
```

#### 使用 BaseHeatmap

```js
import { BaseHeatmap } from 'ray-visutil/lib/heatmap';

function randomFloat(min, max) {
  return Math.random() * (max - min) + min;
}

function randomData(){
  return [
    [-0.91, -1.07, randomFloat(24.0, 28.0)],
    [-2.11, -1.07, randomFloat(24.0, 28.0)],
    [0, 0, randomFloat(24.0, 28.0)],
    [-2.71, -1.07, randomFloat(24.0, 28.0)],
    [-0.86, 1.13, randomFloat(24.0, 28.0)],
    [-0.31, -1.07, randomFloat(24.0, 28.0)],
    [2, -2, randomFloat(24.0, 28.0)],
    [3, 3, randomFloat(24.0, 28.0)]
  ];
}

class HM {
  constructor(param) {
    this._shm = new BaseHeatmap();
    this._params = {};
    this.initParams(param);
  }

  initParams(param = {}) {
    const params = this._params;
    const { width = 10, height = 10, mapSize = 256, transparent, gradient, minValue = 10, maxValue = 50, radius = 0.8, blur = 0.8, data = [] } = param;

    params.areaWidth = width; // area width
    params.areaHeight = height;
    params.mapSize = mapSize;
    params.transparent = !!transparent;
    params.gradient = gradient || {
      0.4: 'blue',
      0.6: 'cyan',
      0.7: 'lime',
      0.8: 'yellow',
      1.0: 'red'
    };

    params.minValue = minValue;
    params.maxValue = maxValue;
    params.radius = radius;
    params.blur = blur;
    params.data = data;

    this._refresh();
  }

  setData(data) {
    this._params.data = data;
    this._refresh();
  }

  _refresh() {
    const params = this._params;

    let rateX = 1;
    let rateY = 1;
    let rateRadius = params.mapSize / params.areaWidth;
    if (params.areaWidth !== params.areaHeight) {
      if (params.areaWidth > params.areaHeight) {
        rateX = 1;
        rateY = params.areaHeight / params.areaWidth;
      } else {
        rateX = params.areaWidth / params.areaHeight;
        rateY = 1;

        rateRadius = params.mapSize / params.areaHeight;
      }
    }

    let scaler = 1;
    if (params.radius * rateRadius < 1) {
      scaler = (1 / params.radius) * rateRadius + 0.0001;
    }

    const data = params.data.map(item => {
      return [item[0] * scaler, item[1] * scaler, item[2]];
    });

    this._shm
      .setCanvasSize(Math.floor(params.mapSize * rateX), Math.floor(params.mapSize * rateY))
      .setAreaSize(params.areaWidth * scaler, params.areaHeight * scaler)
      .setMin(params.minValue)
      .setMax(params.maxValue)
      .setTransparent(params.transparent)
      .setGradient(params.gradient)
      .setRadius(Math.floor(params.radius * rateRadius * scaler), Math.floor(params.blur * rateRadius * scaler))
      .data(data)
      .draw();
  }

  get canvas(){
    return this._shm.getCanvas();
  }
}

// 创建热图
const heatMap01 = new HM({
  mapSize: 512,
  width: 12, // 宽度 单位米
  height: 8, // 长度 单位米
  minValue: 10,
  maxValue: 25,
  radius: 1, // 单个点的热力影响半径
  transparent: false // 未插值区域是否透明（默认为 false ）
});

// 更新热图数值
setInterval(() => {
  const data = randomData();

  heatMap01.setData(data);
}, 1000);

document.body.appendChild(hm.canvas);
```

### UndoPool

前进/后退 池

直接使用：

```js
const undoPool = new UndoPool();
undoPool.add({
  undo() {
    // ...
  },
  redo() {
    // ...
  }
});
```

#### 使用案例

```js
const undoPool = new UndoPool();
const people = {};

function addPerson(id, name) {
  people[id] = name;
};

function removePerson(id) {
  delete people[id];
};

function createPerson(id, name) {
  // first creation
  addPerson(id, name);

  // make undo-able
  undoPool.add({
    undo() {
      removePerson(id)
    },
    redo() {
      addPerson(id, name);
    }
  });
}

createPerson(101, "John");
createPerson(102, "Mary");

console.log("people", people); // {101: "John", 102: "Mary"}

undoPool.undo();
console.log("people", people); // {101: "John"}

undoPool.undo();
console.log("people", people); // {}

undoPool.redo();
console.log("people", people); // {101: "John"}
```

#### UndoPool methods

* `new UndoPool($limit?: Number, cb?: Function)`: create undopool.
* `undoPool.undo();`: Performs the undo action.
* `undoPool.redo();`: Performs the redo action.
* `undoPool.clear();`: Clears all stored states.
* `undoPool.setLimit(limit);`: Set the maximum number of undo steps. Default: 0 (unlimited).
* `var hasUndo = undoPool.hasUndo();`: Tests if any undo actions exist.
* `var hasRedo = undoPool.hasRedo();`: Tests if any redo actions exist.
* `undoPool.setCallback(myCallback);`: Get notified on changes.
* `var index = undoPool.getIndex();`: Returns the index of the actions list.

### toImage

* imageUtils
* `toVizBlob(node, options)`
* `base64DataToBlob(data)`
* `base64DataToFile(data, filename)`

#### imageUtils

* `toSvg(node, options)`
* `toPng(node, options)`
* `toJpeg(node, options)`
* `toBlob(node, options)`
* `toPixelData(node, options)`

options:

* filter: func 过滤node节点，如 `(node) => node.tagName !== 'i'`
* bgcolor: background color, valid CSS color value.
* height, width: 设置 node 的宽高
* twidth, theight: 设置裁剪后自定义宽高
* style: 设置 node 样式
* quality: JPEG 时，设置 quality， 0 and 1，(e.g. 0.92 => 92%)
* cacheBust: 启用缓存
* imagePlaceholder: 获取图像失败时的默认url

### cached 使用教程

```js
import registerCache from 'ray-visutil/lib/LoaderCache';

/** 启用 cached，默认会拦截所有的 XMLHttpRequest */
// 注意: 该方法仅执行一次，确保在入口处执行即可
registerCache({
  dbName: '__br3d',
  onRequest(url){
    let extension = '';
    if (url){
      const strs = url.split('.');
      if (strs.length > 1){
        const ext = strs.pop();
        const index = ext.indexOf('?');
        extension = index !== -1 ? ext.substring(0, index) : ext;
      }
    }
    return ['glb', 'gltf', 'bin', 'json', 'jpg', 'png', 'obj', 'mtl'].includes(extension.toLowerCase());
  }
});
```

## changelog

* 2026-04-23 v1.3.21

  fix `typing`

* 2024-05-13 v1.3.19

  add `apng`

* 2024-04-30 v1.3.17

  add `Clock` and `LibGif`

* 2024-04-23 v1.3.15

  add `Svg Convert`

* 2024-02-23 v1.3.14

  fix `resize` bugs

* 2024-01-15 v1.3.13

  修复 `typing` 数据中断无法继续执行问题

* 2021-12-22 v1.3.7

  修复 `imageUtils` 无法截取 svg use symbol 问题

* 2021-5-7 v1.3.1

  add `toos#utf8ArrayToStr`

* 2021-2-23 v1.2.0

  add `memorize` and `memoFastStringify`

* 2021-2-23 v1.1.15

  add `UndoPoll` ts support and `UndoPool($limit?: Number, cb?: Function)` constructor

* 2020-8-12 v1.1.12

  add `imageUtils`

* 2020-7-7 v1.1.10

  add `QRTypes`

* 2020-7-6 v1.1.9

  add `VisCache`

* 2020-5-25 v1.1.8

  add `fastStringify`

* 2020-5-9 v1.1.7

  add `uuid、shortid`

* 2020-5-8 v1.1.6

  add `UndoPool`

* 2020-4-27 v1.1.5

  modify `tools`

* 2019-12-31 v1.1.3

  add screen log

* 2019-12-9 v1.1.2

  add imageProcessor

* 2018-12-19 v1.0.9

  add tools

* 2018-12-18 v1.0.8

  add Transition

## License

MIT

---
_Source: https://npm.io/package/ray-visutil · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
