typewriter-vue
Version:
Typing effect component for Vue.js
505 lines (441 loc) • 22.2 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('regenerator-runtime/runtime.js'), require('core-js/modules/es.number.constructor.js'), require('core-js/modules/es.string.trim.js'), require('core-js/modules/es.regexp.exec.js'), require('core-js/modules/es.string.replace.js'), require('core-js/modules/es.object.to-string.js'), require('core-js/modules/es.array.slice.js'), require('core-js/modules/es.string.split.js'), require('core-js/modules/es.regexp.constructor.js'), require('core-js/modules/es.regexp.to-string.js'), require('core-js/modules/es.string.match.js')) :
typeof define === 'function' && define.amd ? define(['exports', 'regenerator-runtime/runtime.js', 'core-js/modules/es.number.constructor.js', 'core-js/modules/es.string.trim.js', 'core-js/modules/es.regexp.exec.js', 'core-js/modules/es.string.replace.js', 'core-js/modules/es.object.to-string.js', 'core-js/modules/es.array.slice.js', 'core-js/modules/es.string.split.js', 'core-js/modules/es.regexp.constructor.js', 'core-js/modules/es.regexp.to-string.js', 'core-js/modules/es.string.match.js'], factory) :
(global = global || self, factory(global.Typewriter = {}));
}(this, (function (exports) { 'use strict';
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
//
//
//
//
//
//
var assert = function assert(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
};
var script = {
name: "Typewriter",
props: {
/**
* Time to wait before typing first character (ms).
*/
startDelay: {
type: Number,
default: 0
},
/**
* Interval between entering letters (ms).
*/
typeInterval: {
type: Number,
default: 75
},
/**
* Array of objects with keys:
* - from - @type {String} to be replaced (has to be present in currently displayed text),
* - to - @type {String} that will replace 'from' text
*/
replace: {
type: Array,
default: function _default() {
return [];
}
},
/**
* Interval between replacing in (ms).
*/
replaceInterval: {
type: Number,
default: 2000
}
},
mounted: function mounted() {
this.init();
},
methods: {
init: function init() {
var _this = this;
return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var _this$$el, innerHTML, innerText;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_this$$el = _this.$el, innerHTML = _this$$el.innerHTML, innerText = _this$$el.innerText;
_this.$el.innerHTML = innerHTML.trim() === innerText ? "<span>".concat(innerHTML, "</span>") : innerHTML;
_context.next = 4;
return _this.typewriter(_this.$el.innerHTML);
case 4:
if (_this.replace.length) {
setTimeout(function () {
_this.startReplacing();
}, _this.replaceInterval);
}
case 5:
case "end":
return _context.stop();
}
}
}, _callee);
}))();
},
typewriter: function typewriter(str) {
var _this2 = this;
return new Promise(function (resolve) {
_this2.$el.innerHTML = "";
var f = function f(index) {
var current = str[index];
index = current === "<" ? str.indexOf(">", index) + 1 : ++index;
_this2.$el.innerHTML = str.substr(0, index);
if (index < str.length - 1) {
setTimeout(f, _this2.typeInterval, index);
return;
}
resolve();
};
f(0);
});
},
removeString: function removeString(start, end) {
var _this3 = this;
return new Promise(function (resolve) {
var elementCopy = _this3.$el;
var f = function f(index) {
elementCopy.innerHTML = elementCopy.innerHTML.slice(0, index);
index--;
if (start <= index) {
setTimeout(f, _this3.typeInterval, index);
return;
}
resolve();
};
f(end - 1);
});
},
addString: function addString(start, str) {
var _this4 = this;
return new Promise(function (resolve) {
var elementCopy = _this4.$el;
var f = function f(index, start) {
elementCopy.innerHTML = _this4.insert(elementCopy.innerHTML, start, str[index]);
if (index < str.length - 1) {
setTimeout(f, _this4.typeInterval, ++index, ++start);
return;
}
resolve();
};
f(0, start);
});
},
insert: function insert(text, index, newChar) {
return text.substring(0, index) + newChar + text.substr(index);
},
replaceLastWord: function replaceLastWord(to) {
var _this5 = this;
return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
var lastWord;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
lastWord = _this5.$el.innerText.split(" ").pop();
assert(lastWord, "Component`s current innerHTML is empty");
_context2.next = 4;
return _this5.replaceText({
from: lastWord,
to: to
});
case 4:
case "end":
return _context2.stop();
}
}
}, _callee2);
}))();
},
replaceText: function replaceText(changed) {
var _this6 = this;
return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {
var from, to, str, regex, match, index;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
assert(changed, "Changed parameter is needed");
from = changed.from, to = changed.to;
str = _this6.$el.innerHTML;
regex = new RegExp("\\b" + from + "\\b");
match = str.match(regex);
assert(match, "Substring '".concat(from, "' not found in component` current innerHTML"));
index = match.index;
_context3.next = 9;
return _this6.removeString(index, index + from.length);
case 9:
_context3.next = 11;
return _this6.addString(index, to);
case 11:
case "end":
return _context3.stop();
}
}
}, _callee3);
}))();
},
startReplacing: function startReplacing() {
var _this7 = this;
var replace = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.replace;
var replaceInterval = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.replaceInterval;
if (!replace) {
throw new Error("Replace parameter is needed");
}
if (!replace) {
throw new Error("Replace parameter has 0 length");
}
return new Promise(function (resolve) {
var func = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(index) {
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
_context4.next = 2;
return _this7.replaceText(replace[index]);
case 2:
if (!(index < replace.length - 1)) {
_context4.next = 5;
break;
}
setTimeout(func, replaceInterval, ++index);
return _context4.abrupt("return");
case 5:
resolve();
case 6:
case "end":
return _context4.stop();
}
}
}, _callee4);
}));
return function func(_x) {
return _ref.apply(this, arguments);
};
}();
func(0);
});
}
}
};
function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier /* server only */, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
if (typeof shadowMode !== 'boolean') {
createInjectorSSR = createInjector;
createInjector = shadowMode;
shadowMode = false;
}
// Vue.extend constructor export interop.
const options = typeof script === 'function' ? script.options : script;
// render functions
if (template && template.render) {
options.render = template.render;
options.staticRenderFns = template.staticRenderFns;
options._compiled = true;
// functional template
if (isFunctionalTemplate) {
options.functional = true;
}
}
// scopedId
if (scopeId) {
options._scopeId = scopeId;
}
let hook;
if (moduleIdentifier) {
// server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext); // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__;
}
// inject component styles
if (style) {
style.call(this, createInjectorSSR(context));
}
// register component module identifier for async chunk inference
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier);
}
};
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook;
}
else if (style) {
hook = shadowMode
? function (context) {
style.call(this, createInjectorShadow(context, this.$root.$options.shadowRoot));
}
: function (context) {
style.call(this, createInjector(context));
};
}
if (hook) {
if (options.functional) {
// register for functional component in vue file
const originalRender = options.render;
options.render = function renderWithStyleInjection(h, context) {
hook.call(context);
return originalRender(h, context);
};
}
else {
// inject component registration as beforeCreate hook
const existing = options.beforeCreate;
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
}
}
return script;
}
const isOldIE = typeof navigator !== 'undefined' &&
/msie [6-9]\\b/.test(navigator.userAgent.toLowerCase());
function createInjector(context) {
return (id, style) => addStyle(id, style);
}
let HEAD;
const styles = {};
function addStyle(id, css) {
const group = isOldIE ? css.media || 'default' : id;
const style = styles[group] || (styles[group] = { ids: new Set(), styles: [] });
if (!style.ids.has(id)) {
style.ids.add(id);
let code = css.source;
if (css.map) {
// https://developer.chrome.com/devtools/docs/javascript-debugging
// this makes source maps inside style tags work properly in Chrome
code += '\n/*# sourceURL=' + css.map.sources[0] + ' */';
// http://stackoverflow.com/a/26603875
code +=
'\n/*# sourceMappingURL=data:application/json;base64,' +
btoa(unescape(encodeURIComponent(JSON.stringify(css.map)))) +
' */';
}
if (!style.element) {
style.element = document.createElement('style');
style.element.type = 'text/css';
if (css.media)
style.element.setAttribute('media', css.media);
if (HEAD === undefined) {
HEAD = document.head || document.getElementsByTagName('head')[0];
}
HEAD.appendChild(style.element);
}
if ('styleSheet' in style.element) {
style.styles.push(code);
style.element.styleSheet.cssText = style.styles
.filter(Boolean)
.join('\n');
}
else {
const index = style.ids.size - 1;
const textNode = document.createTextNode(code);
const nodes = style.element.childNodes;
if (nodes[index])
style.element.removeChild(nodes[index]);
if (nodes.length)
style.element.insertBefore(textNode, nodes[index]);
else
style.element.appendChild(textNode);
}
}
}
/* script */
const __vue_script__ = script;
/* template */
var __vue_render__ = function() {
var _vm = this;
var _h = _vm.$createElement;
var _c = _vm._self._c || _h;
return _c("div", { staticClass: "content" }, [_vm._t("default")], 2)
};
var __vue_staticRenderFns__ = [];
__vue_render__._withStripped = true;
/* style */
const __vue_inject_styles__ = function (inject) {
if (!inject) return
inject("data-v-b6673ec6_0", { source: "\n@keyframes blink {\nfrom,\n to {\n opacity: 0;\n}\n50% {\n opacity: 1;\n}\n}\n.content *:last-child::after {\n font-size: calc(1em + 2px);\n content: \"|\";\n animation: blink 0.75s step-end infinite;\n}\n", map: {"version":3,"sources":["/home/runner/work/typewriter-vue/typewriter-vue/src/components/Typewriter.vue"],"names":[],"mappings":";AAkKA;AACA;;IAEA,UAAA;AACA;AACA;IACA,UAAA;AACA;AACA;AAEA;EACA,0BAAA;EACA,YAAA;EACA,wCAAA;AACA","file":"Typewriter.vue","sourcesContent":["<template>\n <div class=\"content\">\n <slot></slot>\n </div>\n</template>\n\n<script>\nconst assert = (condition, message) => {\n if (!condition) {\n throw new Error(message || \"Assertion failed\");\n }\n};\n\nexport default {\n name: \"Typewriter\",\n props: {\n /**\n * Time to wait before typing first character (ms).\n */\n startDelay: {\n type: Number,\n default: 0,\n },\n /**\n * Interval between entering letters (ms).\n */\n typeInterval: {\n type: Number,\n default: 75,\n },\n /**\n * Array of objects with keys:\n * - from - @type {String} to be replaced (has to be present in currently displayed text),\n * - to - @type {String} that will replace 'from' text\n */\n replace: {\n type: Array,\n default: () => [],\n },\n /**\n * Interval between replacing in (ms).\n */\n replaceInterval: {\n type: Number,\n default: 2000,\n },\n },\n mounted() {\n this.init();\n },\n methods: {\n async init() {\n const { innerHTML, innerText } = this.$el;\n this.$el.innerHTML =\n innerHTML.trim() === innerText\n ? `<span>${innerHTML}</span>`\n : innerHTML;\n await this.typewriter(this.$el.innerHTML);\n if (this.replace.length) {\n setTimeout(() => {\n this.startReplacing();\n }, this.replaceInterval);\n }\n },\n typewriter(str) {\n return new Promise((resolve) => {\n this.$el.innerHTML = \"\";\n const f = (index) => {\n const current = str[index];\n index = current === \"<\" ? str.indexOf(\">\", index) + 1 : ++index;\n this.$el.innerHTML = str.substr(0, index);\n if (index < str.length - 1) {\n setTimeout(f, this.typeInterval, index);\n return;\n }\n resolve();\n };\n f(0);\n });\n },\n removeString(start, end) {\n return new Promise((resolve) => {\n const elementCopy = this.$el;\n const f = (index) => {\n elementCopy.innerHTML = elementCopy.innerHTML.slice(0, index);\n index--;\n if (start <= index) {\n setTimeout(f, this.typeInterval, index);\n return;\n }\n resolve();\n };\n f(end - 1);\n });\n },\n addString(start, str) {\n return new Promise((resolve) => {\n const elementCopy = this.$el;\n const f = (index, start) => {\n elementCopy.innerHTML = this.insert(\n elementCopy.innerHTML,\n start,\n str[index]\n );\n if (index < str.length - 1) {\n setTimeout(f, this.typeInterval, ++index, ++start);\n return;\n }\n resolve();\n };\n f(0, start);\n });\n },\n insert(text, index, newChar) {\n return text.substring(0, index) + newChar + text.substr(index);\n },\n async replaceLastWord(to) {\n const lastWord = this.$el.innerText.split(\" \").pop();\n assert(lastWord, \"Component`s current innerHTML is empty\");\n await this.replaceText({ from: lastWord, to });\n },\n async replaceText(changed) {\n assert(changed, \"Changed parameter is needed\");\n const { from, to } = changed;\n const str = this.$el.innerHTML;\n const regex = new RegExp(\"\\\\b\" + from + \"\\\\b\");\n const match = str.match(regex);\n assert(\n match,\n `Substring '${from}' not found in component\\` current innerHTML`\n );\n const { index } = match;\n await this.removeString(index, index + from.length);\n await this.addString(index, to);\n },\n startReplacing(\n replace = this.replace,\n replaceInterval = this.replaceInterval\n ) {\n if (!replace) {\n throw new Error(\"Replace parameter is needed\");\n }\n if (!replace) {\n throw new Error(\"Replace parameter has 0 length\");\n }\n return new Promise((resolve) => {\n const func = async (index) => {\n await this.replaceText(replace[index]);\n if (index < replace.length - 1) {\n setTimeout(func, replaceInterval, ++index);\n return;\n }\n resolve();\n };\n func(0);\n });\n },\n },\n};\n</script>\n\n<style>\n@keyframes blink {\n from,\n to {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n}\n\n.content *:last-child::after {\n font-size: calc(1em + 2px);\n content: \"|\";\n animation: blink 0.75s step-end infinite;\n}\n</style>\n"]}, media: undefined });
};
/* scoped */
const __vue_scope_id__ = undefined;
/* module identifier */
const __vue_module_identifier__ = undefined;
/* functional template */
const __vue_is_functional_template__ = false;
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__ = /*#__PURE__*/normalizeComponent(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
__vue_script__,
__vue_scope_id__,
__vue_is_functional_template__,
__vue_module_identifier__,
false,
createInjector,
undefined,
undefined
);
function install(Vue) {
if (install.installed) return;
install.installed = true;
Vue.component("Typewriter", __vue_component__);
} // Create module definition for Vue.use()
var plugin = {
install: install
}; // Auto-install when vue is found (eg. in browser via <script> tag)
var GlobalVue = null;
if (typeof window !== "undefined") {
GlobalVue = window.Vue;
} else if (typeof global !== "undefined") {
GlobalVue = global.Vue;
}
if (GlobalVue) {
GlobalVue.use(plugin);
} // To allow use as module (npm/webpack/etc.) export component
exports.default = __vue_component__;
exports.install = install;
Object.defineProperty(exports, '__esModule', { value: true });
})));