├── .gitignore ├── Gulpfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── example ├── index.html └── index.jade ├── package.json ├── src └── typeout.js ├── todo.md └── typeout.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var plugins = require("gulp-load-plugins")(); 3 | var stylish = require('jshint-stylish'); 4 | var browserify = require('browserify'); 5 | var transform = require('vinyl-transform'); 6 | var to5 = require("gulp-6to5"); 7 | 8 | var jsFileGlob = 'src/typeout.js'; 9 | var jadeFileGlob = 'example/*.jade'; 10 | 11 | gulp.task('js:lint', function() { 12 | return gulp.src(jsFileGlob) 13 | .pipe(plugins.jshint()) 14 | .pipe(plugins.jshint.reporter('jshint-stylish')); 15 | }); 16 | 17 | gulp.task('js:build', function() { 18 | var browserified = transform(function(filename) { 19 | var b = browserify(filename); 20 | return b.bundle(); 21 | }); 22 | 23 | return gulp.src(jsFileGlob) 24 | .pipe(browserified) 25 | .pipe(to5()) 26 | .pipe(plugins.uglify()) 27 | .pipe(plugins.rename({suffix: '.min'})) 28 | .pipe(gulp.dest('./')); 29 | }); 30 | 31 | gulp.task('jade:build', function() { 32 | return gulp.src(jadeFileGlob) 33 | .pipe(plugins.jade()) 34 | .pipe(gulp.dest('./example/')); 35 | }); 36 | 37 | gulp.task('watch', function() { 38 | gulp.watch(jsFileGlob, ['js:lint', 'js:build']); 39 | gulp.watch(jadeFileGlob, ['jade:build']); 40 | }); 41 | 42 | gulp.task('default', ['js:lint', 'js:build', 'jade:build', 'watch']); -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Connor Atherton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## typeout 2 | 3 | Produces a nice text effect increasingly adopted by companies like [Google](), [npm]() and more. 4 | 5 | [See a demo](http://htmlpreview.github.io/?https://github.com/ConnorAtherton/typeout/blob/master/example/index.html) 6 | 7 | ## how to use 8 | 9 | ``` js 10 | /* 11 | * @params selector [string] - 12 | * @params wordList [array] - Contains all the word to write 13 | * @params options [object] - Override default options (shown below) 14 | */ 15 | 16 | typeout(selector, word_list, options); 17 | ``` 18 | 19 | Usually the selector will reference a **span** element or some other 20 | element that is displaying inline. But it will work with any element. 21 | 22 | ```html 23 |

24 | San Francisco is amazing 25 |

26 | ``` 27 | 28 | Any html child element of the selector element will automatically be 29 | appended to the append list. In the example above the word *amazing* 30 | will be added to the world list you pass in. 31 | 32 | ```js 33 | // basic usage with just one loop 34 | typeout('.typeout', ['wonderful', 'eye-opening', 'an experience'], { 35 | callback: function(el) { 36 | el.innerHTML += "."; 37 | } 38 | }); 39 | ``` 40 | 41 | The code above will typeout all words in the word list once and on completion 42 | will add a period (.) at the end. 43 | 44 | ### default options 45 | ```js 46 | var defaults = { 47 | interval: 3000, 48 | completeClass: 'typeout-complete', 49 | callback : function noop() {}, 50 | max: 110, // upper limit for typing speed 51 | min: 40, // lower limit for typing speed 52 | numLoops: 1 // number of loops before the callback is called 53 | }; 54 | ``` 55 | 56 | You can have an infinite loop passing numLoops as Infinity, but I wouldn't recommend it. 57 | Unless I add es6 generators at some point. 58 | 59 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typeout", 3 | "version": "0.7.0", 4 | "authors": [ 5 | "Connor Atherton " 6 | ], 7 | "description": "Type and retype text in the browser.", 8 | "main": "typeout.min.js", 9 | "keywords": [ 10 | "browser", 11 | "type", 12 | "typeout" 13 | ], 14 | "license": "MIT", 15 | "homepage": "https://github.com/ConnorAtherton/typeout", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | Typeout Demo

San Francisco is great

-------------------------------------------------------------------------------- /example/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Typeout Demo 5 | link(href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700' rel='stylesheet' type='text/css') 6 | 7 | style. 8 | html, body { 9 | font-family: 'Source Sans Pro'; 10 | font-size: 25px; 11 | line-height: 1.2em; 12 | margin: 0; padding: 0; 13 | height: 100%; 14 | } 15 | 16 | #demo { 17 | position: relative; 18 | height: auto; 19 | top: 40%; 20 | text-align: center; 21 | letter-spacing: -1px; 22 | font-weight: 300; 23 | text-transform: lowercase; 24 | } 25 | 26 | .typeout { 27 | color: #65d165; 28 | } 29 | 30 | .typeout:after { 31 | -webkit-animation: 0.7s blink step-end infinite; 32 | -moz-animation: 0.7s blink step-end infinite; 33 | -ms-animation: 0.7s blink step-end infinite; 34 | -o-animation: 0.7s blink step-end infinite; 35 | animation: 0.7s blink step-end infinite; 36 | content: '|'; 37 | padding-left: 2px; 38 | } 39 | .typeout.typeout-complete:after { 40 | content: none; 41 | } 42 | 43 | @keyframes "blink" { 44 | from, to { 45 | opacity: 0; 46 | } 47 | 50% { 48 | opacity: 1; 49 | } 50 | } 51 | 52 | @-moz-keyframes blink { 53 | from, to { 54 | opacity: 0; 55 | } 56 | 50% { 57 | opacity: 1; 58 | } 59 | } 60 | 61 | @-webkit-keyframes "blink" { 62 | from, to { 63 | opacity: 0; 64 | } 65 | 50% { 66 | opacity: 1; 67 | } 68 | } 69 | 70 | @-ms-keyframes "blink" { 71 | from,to { 72 | opacity: 0; 73 | } 74 | 50% { 75 | opacity: 1; 76 | } 77 | 78 | } 79 | 80 | @-o-keyframes "blink" { 81 | from,to { 82 | opacity: 0; 83 | } 84 | 50% { 85 | opacity: 1; 86 | } 87 | } 88 | 89 | body 90 |

San Francisco is great

91 | 92 | script(src="../typeout.min.js") 93 | script. 94 | typeout('.typeout', ['wonderful', 'eye-opening', 'amazing', 'an experience'], { 95 | numLoops: 3, 96 | callback: function(el) { 97 | console.log('calling the callback baby'); 98 | el.innerHTML += "."; 99 | } 100 | }); 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typeout", 3 | "version": "0.7.0", 4 | "description": "Type and retype text in the browser.", 5 | "main": "typeout.min.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "scripts": {}, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:ConnorAtherton/typeout.git" 13 | }, 14 | "author": "Connor Atherton", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/ConnorAtherton/typeout/issues" 18 | }, 19 | "homepage": "https://github.com/ConnorAtherton/typeout", 20 | "devDependencies": { 21 | "browserify": "^8.0.3", 22 | "gulp": "^3.8.10", 23 | "gulp-jshint": "^1.9.0", 24 | "gulp-load-plugins": "^0.8.0", 25 | "gulp-uglify": "^1.0.2", 26 | "jshint-stylish": "^1.0.0", 27 | "vinyl-transform": "^1.0.0", 28 | "gulp-jade": "^0.10.0", 29 | "gulp-rename": "^1.2.0" 30 | }, 31 | "dependencies": { 32 | "aqueue": "0.3.0", 33 | "gulp-6to5": "^3.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/typeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * typeout.js 3 | * 4 | * Copyright 2014, Connor Atherton - http://connoratherton.com/ 5 | * Released under the MIT Licence 6 | * http://opensource.org/licenses/MIT 7 | * 8 | * Github: http://github.com/ConnorAtherton/typeout 9 | */ 10 | 11 | "use strict"; 12 | // TODO Take gif of it working and add it to readme 13 | 14 | let aq = require('aqueue'); 15 | 16 | /* 17 | * Default options. 18 | */ 19 | var defaults = { 20 | interval: 3000, 21 | completeClass: 'typeout-complete', 22 | callback : function noop() {}, 23 | numLoops: 1, 24 | max: 110, 25 | min: 40 26 | }; 27 | 28 | var typeout = function(selector, words, options) { 29 | options = merge(defaults, (options || {})); 30 | options.words = words; 31 | 32 | var el = document.querySelector(selector); 33 | var shouldStartType = true; 34 | var aqueue = aq(); 35 | let numLoops = 0; 36 | 37 | initialSetup(); 38 | 39 | function initialSetup() { 40 | var html = el.innerHTML.trim(); 41 | if (html !== "") { 42 | shouldStartType = false; 43 | options.words.unshift(html); 44 | el.innerHTML = ''; 45 | } 46 | 47 | // start the spin loop 48 | startLoop(); 49 | } 50 | 51 | function startLoop() { 52 | var stop = false; 53 | var currentElIndex = 0; 54 | var listLength = options.words.length; 55 | var stopping = false; 56 | 57 | options.words.forEach(function(el, i) { 58 | aqueue(type, options.words[currentElIndex]); 59 | 60 | if (currentElIndex === listLength - 1) { 61 | numLoops++; 62 | 63 | if (numLoops !== Infinity && numLoops === options.numLoops) { 64 | return aqueue(callback, null); 65 | } 66 | 67 | // Inifinite loop -> wouldn't advise but possible 68 | aqueue(pause, options.interval)(deleteWord); 69 | return startLoop(); 70 | } else { 71 | aqueue(pause, options.interval)(deleteWord); 72 | } 73 | 74 | currentElIndex++; 75 | 76 | if (currentElIndex === listLength) currentElIndex = 0; 77 | }); 78 | } 79 | 80 | function type(word, next) { 81 | var progress = 0; 82 | var wordLength = word.length; 83 | 84 | var interval = setInterval(function() { 85 | 86 | if (progress < wordLength) { 87 | el.innerHTML += word[progress]; 88 | progress++; 89 | } else { 90 | window.clearInterval(interval); 91 | next(); 92 | } 93 | 94 | }, getSpeed()); 95 | } 96 | 97 | /* 98 | * BUG - aq won't invoke the function if no argument is passed which is kinda weird 99 | */ 100 | function deleteWord(next) { 101 | var interval = setInterval(function() { 102 | var current = el.innerHTML; 103 | var length = current.length; 104 | 105 | if (!length) { 106 | clearInterval(interval); 107 | next(); 108 | } 109 | 110 | el.innerHTML = current.substring(0, length - 1); 111 | }, getSpeed()); 112 | } 113 | 114 | function getSpeed() { 115 | var max = options.max; 116 | var min = options.min; 117 | 118 | return Math.floor(Math.random() * (max - min + 1) + min); 119 | } 120 | 121 | function pause(timeout, next) { 122 | setTimeout(function() { 123 | next(); 124 | }, timeout); 125 | } 126 | 127 | function callback() { 128 | addClass(el, options.completeClass); 129 | options.callback(el); 130 | } 131 | }; 132 | 133 | /* 134 | * Merges two objects together. 135 | * 136 | * Properties in the second object have higher 137 | * precedence than corresponding properties in 138 | * the first 139 | */ 140 | function merge(source, target) { 141 | let obj = {}; 142 | 143 | for (let key in source) { 144 | if (source.hasOwnProperty(key)) { 145 | obj[key] = (typeof target[key] !== "undefined") ? target[key] : source[key]; 146 | } 147 | } 148 | 149 | return obj; 150 | } 151 | 152 | /* 153 | * Adds a class. 154 | * 155 | * Used when the word rotation is finished. 156 | */ 157 | function addClass(el, klass) { 158 | el.className += ' ' + klass; 159 | } 160 | 161 | global.typeout = typeout; 162 | 163 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | #TODOS 2 | 3 | ##src/typeout.js 4 | - Take gif of it working and add it to readme 5 | -------------------------------------------------------------------------------- /typeout.min.js: -------------------------------------------------------------------------------- 1 | "use strict";!function n(e,t,r){function o(u,f){if(!t[u]){if(!e[u]){var a="function"==typeof require&&require;if(!f&&a)return a(u,!0);if(i)return i(u,!0);var c=new Error("Cannot find module '"+u+"'");throw c.code="MODULE_NOT_FOUND",c}var l=t[u]={exports:{}};e[u][0].call(l.exports,function(n){var t=e[u][1][n];return o(t?t:n)},l,l.exports,n,e,t,r)}return t[u].exports}for(var i="function"==typeof require&&require,u=0;ut?(p.innerHTML+=n[t],t++):(window.clearInterval(o),e())},s())}function l(n){var e=setInterval(function(){var t=p.innerHTML,r=t.length;r||(clearInterval(e),n()),p.innerHTML=t.substring(0,r-1)},s())}function s(){var n=u.max,e=u.min;return Math.floor(Math.random()*(n-e+1)+e)}function v(n,e){setTimeout(function(){e()},n)}function d(){r(p,u.completeClass),u.callback(p)}u=t(i,u||{}),u.words=e;var p=document.querySelector(n),m=!0,h=o(),y=0;f()};e.typeout=u}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{aqueue:1}]},{},[2]); --------------------------------------------------------------------------------