This repository was archived by the owner on Jul 18, 2018. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathjquery.jtype.js
More file actions
182 lines (156 loc) · 5.65 KB
/
Copy pathjquery.jtype.js
File metadata and controls
182 lines (156 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
(function($) {
/**
* Generates text using a typewriter animation. It is possible to vary the speed and number of errors produced
* either by modifying the defaults (jQuery.fn.jtype.defaultOpts) or by passing additional arguments to
* the method call.
*
* @param {String} text the text to be appended to the jQuery wrapped set
* @param {Object} [opts] any arguments here override the values in jQuery.fn.jtype.defaultOpts
*/
jQuery.fn.jtype = function(text, opts) {
var $out = $(this);
if( $out.data('jtype-abort') ) { $out.data('jtype-abort')(); }
var tw = new Typewriter($.extend({}, jQuery.fn.jtype.defaultOpts, opts, {out: $out, text: text}));
$out.data('jtype-abort', function() {
tw.end.call(tw);
});
return this;
};
// add defaults to global scope so they can be overridden by hack-happy developers
jQuery.fn.jtype.defaultOpts = {
speed: 30,
variance: 80,
startDelay: 0,
pause: null,
errPercent: 3,
errSpeed: 200,
hide: false, // false = don't hide, any integer = fade out in this many milliseconds
prompt: '<span class="prompt"> </span>',
complete: null
//todo text: 'format', // format=replace <div|p|br|blockquote> with \n, strip=remove html, escape=print the html
//todo html: 'keep' // keep=allow the html, strip=remove the html, escape=show the html
};
function Typewriter(opts) {
this.opts = opts;
this.tag = '';
this.errs = '';
this.pos = 0;
this.input = this.opts.out.is(':input');
this.prompt = null;
this.opts.start = this.input? opts.out.val() : opts.out.html();
this.curr = opts.start;
this.left = opts.text;
if( this.input ) {
// focus on input fields to create a prompt
this.opts.out.focus();
}
else {
// don't append a visual prompt to input fields
this.prompt = $(opts.prompt).appendTo(this.opts.out);
}
this.timer = setTimeout(_fx(this, this.consume), opts.startDelay);
}
Typewriter.prototype.consume = function() {
if( this.input ) {
this.opts.out.val(this.text());
//this.opts.out.focus();
}
else {
this.prompt.detach();
this.opts.out.html(this.text());
if( this.tag ) {
this.opts.out.find(this.tag.substring(2, this.tag.length-1)).last().append(this.prompt);
}
else {
this.opts.out.append(this.prompt);
}
}
if( this.left || this.errs ) {
this.timer = setTimeout(_fx(this, this.consume), this.delay());
}
else {
this.end();
}
};
Typewriter.prototype.delay = function() {
var opts = this.opts, speed = opts.speed, vary = opts.variance, pause = opts.pause, pos = this.pos,
p = pause && pause.hasOwnProperty(pos)? pause[pos] : 0;
// errors trend to a larger variance when typing as it requires finger travel and mental adjustment
if( this.errs ) { p = opts.errSpeed; }
// if there is a pause specified, use it, if not, use a random variance on the speed
return p || speed+_rand(vary); //todo make the variance prefer smaller adjustments
};
Typewriter.prototype.text = function() {
if( this.errs ) {
this.errs = this.errs.substring(0, -1);
}
else {
this.errs = _errs(this.opts.errPercent);
if( !this.errs ) {
// if there aren't any errors added, then consume some more text
this.eatTags();
this.curr += this.left.substr(0,1);
this.left = this.left.substr(1);
this.pos++;
}
}
return this.curr+this.errs+this.tag;
};
Typewriter.prototype.eatTags = function() {
var left = this.left, pos;
while(left && left.substr(0, 1) == '<') {
this.tag = ''; //reset current temporary closer
// fetch the whole tag
var tag = left.substr(0, left.indexOf('>')+1);
pos = tag.length;
if( pos && tag ) {
this.curr += left.substr(0, pos);
this.left = left = left.substr(pos);
this.pos += pos;
if( !tag.match(/^<(\/|br\b|input)/i) ) {
// if this isn't a closing tag, <br />, or <input ...> then add a closing tag temporarily
var html = /^<[a-z]+/i.exec(tag);
if(html !== null) {
html = html[0].replace('<', '</') + '>';
this.tag = html;
}
}
}
else {
break;
}
}
};
Typewriter.prototype.end = function() {
if( this.timer ) {
clearTimeout(this.timer);
}
if( this.left ) {
this.curr += this.left;
this.left = '';
this.opts.out.html(this.curr);
if( this.prompt ) {this.opts.out.append(this.prompt);}
}
if( this.prompt && this.opts.hide !== false ) {
this.prompt.fadeOut(this.opts.hide);
}
if( this.opts.complete ) {
this.opts.complete.call(this.opts.out[0]);
}
this.opts.out.removeData('jtype-abort');
};
function _errs(errPercent) {
return errPercent && _rand(100) < errPercent? _char() : '';
}
var errChars = 'abcdefghijklmnopqrstuvwxyz,.';
var errLen = errChars.length;
function _char() {
return errChars.substr(_rand(errLen), Math.max(1, _rand(6)-3));
}
function _rand(n) {
return Math.floor(Math.random()*n)+1;
}
function _fx(self, fx) {
return function() { fx.call(self); }
}
})(jQuery);