diff --git a/components/text-animation/README.md b/components/text-animation/README.md
new file mode 100644
index 00000000..55f5c8cb
--- /dev/null
+++ b/components/text-animation/README.md
@@ -0,0 +1,112 @@
+# text-animation
+
+this component is used to animate *text-geometry* text,such as Chinese characters.
+
+## Usage
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+and
+```js
+function textAnimation(textEl, index, position, data) {
+ const baseDelay = 500; // 基础延迟
+ setTimeout(() => {
+ textEl.setAttribute('animation', {
+ property: 'material.opacity',
+ to: 1,
+ dur: 500
+ });
+ }, index * 200 + baseDelay); // 延迟确保逐字显示的效果
+}
+```
+we use the `textAnimation` function to animate the text, in this situation, we set the `property` to `material.opacity` and `to` to 1, and set the `dur` to 500, which means the opacity will change from 0 to 1 in 500ms.Chinese characters will be displayed one by one with a delay of 200ms.
+you can customize the `textAnimation` function according to your needs.
+
+## Tips
+
+### font
+you can use facetype.js to convert ttf or otf to json format, and then use the json file as the `font` attribute of the `text-geometry` component.
+### text-geometry
+text-geometry is a good choice for rendering Chinese characters, but it has some limitations, such as:
+
+- It cannot reuse the same texture for different characters, which means the texture will be loaded multiple times.
+
+so I edited the `text-geometry` component to support the reuse of the same texture for different characters:
+
+```js
+/**
+ * TextGeometry component for A-Frame.
+ */
+require('./lib/FontLoader')
+require('./lib/TextGeometry')
+var debug = AFRAME.utils.debug;
+var error = debug('aframe-text-component:error');
+var fontLoader = new THREE.FontLoader();
+var fontCache = {}; // cache for loaded fonts
+AFRAME.registerComponent('text-geometry', {
+ schema: {
+ bevelEnabled: { default: false },
+ bevelSize: { default: 8, min: 0 },
+ bevelThickness: { default: 12, min: 0 },
+ curveSegments: { default: 12, min: 0 },
+ font: { type: 'asset', default: 'https://rawgit.com/ngokevin/kframe/master/components/text-geometry/lib/helvetiker_regular.typeface.json' },
+ height: { default: 0.05, min: 0 },
+ size: { default: 0.5, min: 0 },
+ style: { default: 'normal', oneOf: ['normal', 'italics'] },
+ weight: { default: 'normal', oneOf: ['normal', 'bold'] },
+ value: { default: '' }
+ },
+
+ /**
+ * Called when component is attached and when component data changes.
+ * Generally modifies the entity based on the data.
+ */
+ update: function (oldData) {
+ var data = this.data;
+ var el = this.el;
+
+ // check if font is already cached
+ if (fontCache[data.font]) {
+ this.createTextGeometry(fontCache[data.font]);
+ } else {
+ if (data.font.constructor === String) {
+ fontLoader.load(data.font, (response) => {
+ fontCache[data.font] = response; // cache font
+ this.createTextGeometry(response);
+ });
+ } else if (data.font.constructor === Object) {
+ this.createTextGeometry(data.font);
+ } else {
+ error('Must provide `font` (typeface.json) or `fontPath` (string) to text component.');
+ }
+ }
+ },
+
+ createTextGeometry: function (font) {
+ var data = this.data;
+ var el = this.el;
+
+ var textData = AFRAME.utils.clone(data);
+ textData.font = font;
+ el.getOrCreateObject3D('mesh', THREE.Mesh).geometry = new THREE.TextGeometry(data.value, textData);
+ }
+});
+```
\ No newline at end of file
diff --git a/components/text-animation/index.js b/components/text-animation/index.js
new file mode 100644
index 00000000..6c96b96f
--- /dev/null
+++ b/components/text-animation/index.js
@@ -0,0 +1,64 @@
+AFRAME.registerComponent('text-animation', {
+ schema: {
+ text: { type: 'string' },
+ font: { type: 'selector' },
+ fontSize: { type: 'number', default: 0.5 },
+ color: { type: 'string', default: '#FFF' },
+ charsPerLine: { type: 'number', default: 10 },
+ indent: { type: 'number', default: 2 },
+ position: { type: 'vec3', default: { x: 0, y: 2, z: -5 } },
+ letterSpacing: { type: 'number', default: 0 },
+ lineHeight: { type: 'number', default: 1.5 },
+ curveSegments: { type: 'number', default: 4 },
+ height: { type: 'number', default: 0.05 },
+ },
+ init: function () {
+ const data = this.data;
+ const el = this.el;
+ const functionName = data._function;
+ delete this._function;
+ const characters = data.text.split('');
+ let currentLine = 0;
+ let charIndex = 0;
+ let index = 0;
+
+ const animateText = () => {
+ if (index >= characters.length) return; // if all characters have been animated, exit function
+ const char = characters[index];
+ if (charIndex >= data.charsPerLine || (currentLine === 0 && charIndex >= data.charsPerLine - data.indent)) {
+ currentLine++;
+ charIndex = 0;
+ }
+ const deltaPosition = {
+ x: (charIndex + (currentLine === 0 ? data.indent : 0)) * (data.fontSize + data.letterSpacing),
+ y: -currentLine * (data.fontSize * data.lineHeight),
+ z: 0
+ };
+ const curPosition = {
+ x: data.position.x + deltaPosition.x,
+ y: data.position.y + deltaPosition.y,
+ z: data.position.z + deltaPosition.z
+ };
+ const textEl = document.createElement('a-entity');
+ textEl.setAttribute('text-geometry', {
+ value: char,
+ font: data.font.getAttribute('src'),
+ size: data.fontSize,
+ bevelEnabled: false,
+ curveSegments: data.curveSegments,
+ height:data.height
+ });
+ textEl.setAttribute('material', { color: data.color, transparent: true, opacity: 0 });
+ textEl.setAttribute('position', curPosition);
+ if (functionName && typeof window[functionName] === 'function') {
+ window[functionName](textEl, index, curPosition, data);
+ }
+ else { console.log('no function provided'); }
+ el.appendChild(textEl);
+ charIndex++;
+ index++;
+ requestAnimationFrame(animateText);
+ };
+ animateText();
+ }
+});
diff --git a/components/text-geometry/index.js b/components/text-geometry/index.js
index 57403cde..5d6c9bb9 100644
--- a/components/text-geometry/index.js
+++ b/components/text-geometry/index.js
@@ -3,25 +3,22 @@
*/
require('./lib/FontLoader')
require('./lib/TextGeometry')
-
var debug = AFRAME.utils.debug;
-
var error = debug('aframe-text-component:error');
-
var fontLoader = new THREE.FontLoader();
-
+var fontCache = {}; // cache for loaded fonts
AFRAME.registerComponent('text-geometry', {
schema: {
- bevelEnabled: {default: false},
- bevelSize: {default: 8, min: 0},
- bevelThickness: {default: 12, min: 0},
- curveSegments: {default: 12, min: 0},
- font: {type: 'asset', default: 'https://rawgit.com/ngokevin/kframe/master/components/text-geometry/lib/helvetiker_regular.typeface.json'},
- height: {default: 0.05, min: 0},
- size: {default: 0.5, min: 0},
- style: {default: 'normal', oneOf: ['normal', 'italics']},
- weight: {default: 'normal', oneOf: ['normal', 'bold']},
- value: {default: ''}
+ bevelEnabled: { default: false },
+ bevelSize: { default: 8, min: 0 },
+ bevelThickness: { default: 12, min: 0 },
+ curveSegments: { default: 12, min: 0 },
+ font: { type: 'asset', default: 'https://rawgit.com/ngokevin/kframe/master/components/text-geometry/lib/helvetiker_regular.typeface.json' },
+ height: { default: 0.05, min: 0 },
+ size: { default: 0.5, min: 0 },
+ style: { default: 'normal', oneOf: ['normal', 'italics'] },
+ weight: { default: 'normal', oneOf: ['normal', 'bold'] },
+ value: { default: '' }
},
/**
@@ -32,19 +29,29 @@ AFRAME.registerComponent('text-geometry', {
var data = this.data;
var el = this.el;
- var mesh = el.getOrCreateObject3D('mesh', THREE.Mesh);
- if (data.font.constructor === String) {
- // Load typeface.json font.
- fontLoader.load(data.font, function (response) {
- var textData = AFRAME.utils.clone(data);
- textData.font = response;
- mesh.geometry = new THREE.TextGeometry(data.value, textData);
- });
- } else if (data.font.constructor === Object) {
- // Set font if already have a typeface.json through setAttribute.
- mesh.geometry = new THREE.TextGeometry(data.value, data);
+ // check if font is already cached
+ if (fontCache[data.font]) {
+ this.createTextGeometry(fontCache[data.font]);
} else {
- error('Must provide `font` (typeface.json) or `fontPath` (string) to text component.');
+ if (data.font.constructor === String) {
+ fontLoader.load(data.font, (response) => {
+ fontCache[data.font] = response; // cache font
+ this.createTextGeometry(response);
+ });
+ } else if (data.font.constructor === Object) {
+ this.createTextGeometry(data.font);
+ } else {
+ error('Must provide `font` (typeface.json) or `fontPath` (string) to text component.');
+ }
}
+ },
+
+ createTextGeometry: function (font) {
+ var data = this.data;
+ var el = this.el;
+
+ var textData = AFRAME.utils.clone(data);
+ textData.font = font;
+ el.getOrCreateObject3D('mesh', THREE.Mesh).geometry = new THREE.TextGeometry(data.value, textData);
}
});