├── .gitignore ├── LICENSE ├── README.md ├── dist └── baffle.min.js ├── package.json ├── src ├── baffle.js ├── index.js ├── obfuscator.js └── utils.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Node stuff. 2 | node_modules/ 3 | npm-debug.log 4 | 5 | # Folder view config. 6 | .DS_Store 7 | .DS_Store? 8 | 9 | # Thumbnail caches. 10 | ._* 11 | Thumbs.db 12 | 13 | # File system stuff. 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | # Test page 18 | index.html 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-present Cam Wiegert 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 | # baffle.js 2 | A tiny javascript library **for obfuscating and revealing text** in DOM elements. 3 | 4 | [camwiegert.github.io/baffle](https://camwiegert.github.io/baffle) 5 | 6 | > baffle.js 7 | 8 | 9 | - ~1.8kb gzipped :zap: 10 | - Dependency-free :tada: 11 | - IE9+ :heavy_check_mark: 12 | 13 | ```javascript 14 | // Select elements and start. 15 | const b = baffle('.someSelector').start(); 16 | 17 | // Do something else. 18 | someAsyncFunction(result => { 19 | // Change the text and reveal over 1500ms. 20 | b.text(text => result.text).reveal(1500); 21 | }); 22 | ``` 23 | 24 | --- 25 | 26 | ## Getting Started 27 | 28 | #### Step 0: Install 29 | 30 | [Download the latest release](https://raw.githubusercontent.com/camwiegert/baffle/master/dist/baffle.min.js) or install with npm. 31 | 32 | ```sh 33 | npm install --save baffle 34 | ``` 35 | 36 | #### Step 1: Reference 37 | If you linked baffle directly in your HTML, you can use `window.baffle`. If you're using a module bundler, you'll need to import baffle. 38 | 39 | ```javascript 40 | // CommonJS 41 | const baffle = require('baffle'); 42 | 43 | // ES6 44 | import baffle from 'baffle'; 45 | ``` 46 | 47 | #### Step 2: Initialize 48 | To initialize baffle, all you need to do is call it with some elements. You can pass a NodeList, Node, or CSS selector. 49 | 50 | ```javascript 51 | // With a selector. 52 | const b = baffle('.baffle'); 53 | 54 | // With a NodeList 55 | const b = baffle(document.querySelectorAll('.baffle')); 56 | 57 | // With a Node 58 | const b = baffle(document.querySelector('.baffle')); 59 | ``` 60 | 61 | #### Step 3: Use It 62 | Once you have a baffle instance, you have access to all of the baffle methods. Usually, you'll want to `b.start()` and, eventually, `b.reveal()`. 63 | 64 | ```javascript 65 | // Start obfuscating... 66 | b.start(); 67 | 68 | // Or stop obfuscating... 69 | b.stop(); 70 | 71 | // Obfuscate once... 72 | b.once(); 73 | 74 | // You can set options after initializing... 75 | b.set({...options}); 76 | 77 | // Or change the text at any time... 78 | b.text(text => 'Hi Mom!'); 79 | 80 | // Eventually, you'll want to reveal your text... 81 | b.reveal(1000); 82 | 83 | // And they're all chainable... 84 | b.start() 85 | .set({ speed: 100 }) 86 | .text(text => 'Hi dad!') 87 | .reveal(1000); 88 | ``` 89 | 90 | ## Options 91 | You can set options on baffle during initialization or anytime afterward with `baffle.set()`. 92 | 93 | ```javascript 94 | // During initialize 95 | baffle('.baffle', { 96 | characters: '+#-•=~*', 97 | speed: 75 98 | }); 99 | 100 | // Any time with set() 101 | b.set({ 102 | characters: '¯\_(ツ)_/¯', 103 | speed: 25 104 | }); 105 | ``` 106 | 107 | ### `options.characters` 108 | > The characters baffle uses to obfuscate your text. It can be a string or an array of characters. 109 | > 110 | > **Default:** `'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz~!@#$%^&*()-+=[]{}|;:,./<>?'` 111 | 112 | ### `options.exclude` 113 | > These are the characters that baffle ignores in your text when obfuscating it. You can pass in an array of characters. 114 | > 115 | > **Default:** `[' ']` 116 | 117 | ### `options.speed` 118 | > This is the frequency (in milliseconds) at which baffle updates your text when running. 119 | > 120 | > **Default:** `50` 121 | 122 | ## Methods 123 | An instance of baffle has six methods, all of which are chainable. 124 | 125 | ### `baffle.once()` 126 | > Obfuscates each element once, using `options.characters`. 127 | 128 | ### `baffle.start()` 129 | > Starts obfuscating your elements, updating every `options.speed` milliseconds. 130 | 131 | ### `baffle.stop()` 132 | > Stops obfuscating your elements. This won't reveal your text. It will only stop updating it. To reveal it, use `reveal()`. 133 | 134 | ### `baffle.reveal([duration], [delay])` 135 | > Reveals your text over `duration` milliseconds (default: `0`), with the option to delay by `delay` milliseconds. 136 | 137 | ### `baffle.set([options])` 138 | > Updates instance options using the passed `options` object. You can set any number of keys, even while running. 139 | 140 | ### `baffle.text(fn)` 141 | > Updates the text in each element of your instance using function `fn`, which receives the current text as it's only parameter. The value returned from `fn` will be used as the new text. 142 | 143 | --- 144 | 145 | - **License** MIT 146 | - **Made by** [Cam Wiegert](http://camwiegert.com) 147 | - **Inspired by** [Oak](http://oak.is/) 148 | -------------------------------------------------------------------------------- /dist/baffle.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * baffle 0.3.6 - A tiny javascript library for obfuscating and revealing text in DOM elements. 3 | * Copyright (c) 2016 Cam Wiegert - https://camwiegert.github.io/baffle 4 | * License: MIT 5 | */ 6 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.baffle=e():t.baffle=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}var i=n(2),o=r(i);t.exports=o["default"]},function(t,e){"use strict";function n(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function r(t,e){return t.split("").map(e).join("")}function i(t){return t[Math.floor(Math.random()*t.length)]}function o(t,e){for(var n=0,r=t.length;n?",exclude:[" "],speed:50},a=function(){function t(e,n){i(this,t),this.options=(0,o.extend)(Object.create(c),n),this.elements=(0,o.getElements)(e).map(s["default"]),this.running=!1}return t.prototype.once=function(){var t=this;return(0,o.each)(this.elements,function(e){return e.write(t.options.characters,t.options.exclude)}),this.running=!0,this},t.prototype.start=function(){var t=this;return clearInterval(this.interval),(0,o.each)(this.elements,function(t){return t.init()}),this.interval=setInterval(function(){return t.once()},this.options.speed),this.running=!0,this},t.prototype.stop=function(){return clearInterval(this.interval),this.running=!1,this},t.prototype.set=function(t){return(0,o.extend)(this.options,t),this.running&&this.start(),this},t.prototype.text=function(t){var e=this;return(0,o.each)(this.elements,function(n){n.text(t(n.value)),e.running||n.write()}),this},t.prototype.reveal=function(){var t=this,e=arguments.length<=0||void 0===arguments[0]?0:arguments[0],n=arguments.length<=1||void 0===arguments[1]?0:arguments[1],r=e/this.options.speed||1,i=function(){clearInterval(t.interval),t.running=!0,t.interval=setInterval(function(){var e=t.elements.filter(function(t){return!t.bitmap.every(function(t){return!t})});(0,o.each)(e,function(e){var n=Math.ceil(e.value.length/r);e.decay(n).write(t.options.characters,t.options.exclude)}),e.length||(t.stop(),(0,o.each)(t.elements,function(t){return t.init()}))},t.options.speed)};return setTimeout(i,n),this},t}();e["default"]=function(t,e){return new a(t,e)}},function(t,e,n){"use strict";function r(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var u=n(1),s=function(){function t(e){o(this,t),this.value=e,this.init()}return t.prototype.init=function(){return this.bitmap=this.value.split("").map(function(){return 1}),this},t.prototype.render=function(){var t=this,e=arguments.length<=0||void 0===arguments[0]?[]:arguments[0],n=arguments.length<=1||void 0===arguments[1]?[]:arguments[1];return e.length?(0,u.mapString)(this.value,function(r,i){return n.indexOf(r)>-1?r:t.bitmap[i]?(0,u.sample)(e):r}):this.value},t.prototype.decay=function(){for(var t=arguments.length<=0||void 0===arguments[0]?1:arguments[0];t--;){var e=(0,u.getTruthyIndices)(this.bitmap);this.bitmap[(0,u.sample)(e)]=0}return this},t.prototype.text=function(){var t=arguments.length<=0||void 0===arguments[0]?this.value:arguments[0];return this.value=t,this.init(),this},t}(),c=function(t){function e(n){o(this,e);var i=r(this,t.call(this,n.textContent));return i.element=n,i}return i(e,t),e.prototype.write=function(t,e){return this.element.textContent=this.render(t,e),this},e}(s);e["default"]=function(t){return new c(t)}}])}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "baffle", 3 | "version": "0.3.6", 4 | "description": "A tiny javascript library for obfuscating and revealing text in DOM elements.", 5 | "main": "dist/baffle.min.js", 6 | "scripts": { 7 | "start": "webpack -wp", 8 | "build": "webpack -p", 9 | "lint": "eslint src/**/*.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/camwiegert/baffle.git" 14 | }, 15 | "homepage": "https://camwiegert.github.io/baffle", 16 | "babel": { 17 | "presets": [ 18 | "es2015" 19 | ], 20 | "plugins": [ 21 | [ 22 | "transform-es2015-classes", 23 | { 24 | "loose": true 25 | } 26 | ] 27 | ] 28 | }, 29 | "eslintConfig": { 30 | "extends": "eslint:recommended", 31 | "env": { 32 | "browser": true, 33 | "node": true 34 | }, 35 | "parserOptions": { 36 | "ecmaVersion": 6, 37 | "sourceType": "module" 38 | } 39 | }, 40 | "author": "Cam Wiegert ", 41 | "license": "MIT", 42 | "devDependencies": { 43 | "babel-core": "^6.10.4", 44 | "babel-loader": "^6.2.4", 45 | "babel-plugin-transform-es2015-classes": "^6.14.0", 46 | "babel-preset-es2015": "^6.9.0", 47 | "eslint": "^3.1.1", 48 | "webpack": "^1.13.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/baffle.js: -------------------------------------------------------------------------------- 1 | import { 2 | each, 3 | extend, 4 | getElements 5 | } from './utils'; 6 | 7 | import Obfuscator from './obfuscator'; 8 | 9 | const defaults = { 10 | characters: 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz~!@#$%^&*()-+=[]{}|;:,./<>?', 11 | exclude: [' '], 12 | speed: 50 13 | }; 14 | 15 | /** 16 | * - Baffle - 17 | * 18 | * Provides an interface to one or many instances of 19 | * the Obfuscator class. This is the public-facing class. 20 | * 21 | * baffle(, [options]); 22 | * 23 | */ 24 | 25 | class Baffle { 26 | 27 | constructor(elements, options) { 28 | this.options = extend(Object.create(defaults), options); 29 | this.elements = getElements(elements).map(Obfuscator); 30 | this.running = false; 31 | } 32 | 33 | /** 34 | * Call the write method on each Obfuscator once, using 35 | * the provided characters. 36 | */ 37 | once() { 38 | each(this.elements, el => el.write(this.options.characters, this.options.exclude)); 39 | this.running = true; 40 | return this; 41 | } 42 | 43 | /** 44 | * Run once() every options.speed milliseconds. 45 | */ 46 | start() { 47 | clearInterval(this.interval); 48 | each(this.elements, el => el.init()); 49 | this.interval = setInterval(() => this.once(), this.options.speed); 50 | this.running = true; 51 | return this; 52 | } 53 | 54 | /** 55 | * Stop any running interval. 56 | */ 57 | stop() { 58 | clearInterval(this.interval); 59 | this.running = false; 60 | return this; 61 | } 62 | 63 | /** 64 | * Set any options provided in the opts object. If 65 | * currently running, restart. 66 | */ 67 | set(opts) { 68 | extend(this.options, opts); 69 | if (this.running) this.start(); 70 | return this; 71 | } 72 | 73 | /** 74 | * Set the text in each element with the return value 75 | * of function fn, which receives the current text as 76 | * its only argument. 77 | */ 78 | text(fn) { 79 | each(this.elements, el => { 80 | el.text(fn(el.value)); 81 | if (!this.running) el.write(); 82 | }); 83 | return this; 84 | } 85 | 86 | /** 87 | * Start a new interval, obfuscating fewer characters 88 | * on each cycle at pace to finish within duration 89 | * milliseconds. Optionally, delay by delay millseconds. 90 | * 91 | * Once all elements are revealed, call stop() and 92 | * initialize each element. 93 | */ 94 | reveal(duration = 0, delay = 0) { 95 | // Number of cycles in duration 96 | let cycles = duration / this.options.speed || 1; 97 | 98 | const run = () => { 99 | clearInterval(this.interval); 100 | this.running = true; 101 | this.interval = setInterval(() => { 102 | 103 | // Get elements that haven't been fully revealed 104 | let elements = this.elements.filter(el => 105 | !el.bitmap.every(bit => !bit)); 106 | 107 | // Decay each by pace and write 108 | each(elements, el => { 109 | let pace = Math.ceil(el.value.length / cycles); 110 | el.decay(pace).write(this.options.characters, this.options.exclude); 111 | }); 112 | 113 | // If all elements are revealed, stop and init 114 | if (!elements.length) { 115 | this.stop(); 116 | each(this.elements, el => el.init()); 117 | } 118 | 119 | }, this.options.speed); 120 | }; 121 | 122 | setTimeout(run, delay); 123 | return this; 124 | } 125 | 126 | } 127 | 128 | // Export a factory function so we don't need 'new'. 129 | export default (elements, options) => new Baffle(elements, options); 130 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import baffle from './baffle'; 2 | module.exports = baffle; 3 | -------------------------------------------------------------------------------- /src/obfuscator.js: -------------------------------------------------------------------------------- 1 | import { 2 | getTruthyIndices, 3 | mapString, 4 | sample 5 | } from './utils'; 6 | 7 | /** 8 | * - Obfuscator - 9 | * 10 | * Provides a low-level interface to obfuscate and reveal 11 | * a string based on its corresponding bitmap. 12 | * 13 | * ('hello', [0,1,0,1,0], '*') => '*e*l*o' 14 | * 15 | */ 16 | class Obfuscator { 17 | 18 | constructor(str) { 19 | this.value = str; 20 | this.init(); 21 | } 22 | 23 | /** 24 | * Set the bitmap to an array of 1s, with length equal to this.value. 25 | */ 26 | init() { 27 | this.bitmap = this.value.split('').map(() => 1); 28 | return this; 29 | } 30 | 31 | /** 32 | * Create and return a string by mapping each character in 33 | * this.value to either one of the provided characters randomly 34 | * or to itself, depending on whether the corresponding bitmap 35 | * index is truthy. 36 | */ 37 | render(characters = [], exclude = []) { 38 | 39 | // If no characters are provided, return the raw value. 40 | if (!characters.length) return this.value; 41 | return mapString(this.value, (char, index) => { 42 | 43 | // Skip any characters that are passed as exclude. 44 | if (exclude.indexOf(char) > -1) return char; 45 | 46 | /** 47 | * If corresponding bitmap index is truthy, return 48 | * a randomly chosen character from characters, else 49 | * return this character. 50 | */ 51 | return this.bitmap[index] ? 52 | sample(characters) : 53 | char; 54 | }); 55 | } 56 | 57 | /** 58 | * Set count of the truthy indices in this.bitmap to 0, 59 | * chosen randomly. 60 | */ 61 | decay(count = 1) { 62 | while (count--) { 63 | let on = getTruthyIndices(this.bitmap); 64 | this.bitmap[sample(on)] = 0; 65 | } 66 | return this; 67 | } 68 | 69 | /** 70 | * Change this.value to a new string and reset this.bitmap 71 | * to match. 72 | */ 73 | text(str = this.value) { 74 | this.value = str; 75 | this.init(); 76 | return this; 77 | } 78 | 79 | } 80 | 81 | /** 82 | * - ObfuscatorElement - 83 | * 84 | * Extends Obfuscator to be able to wrap a DOM element and 85 | * update its textContent. 86 | * 87 | * (

Hi Mom!

).write('*~•+') =>

•~ *+~•

88 | * 89 | */ 90 | class ObfuscatorElement extends Obfuscator { 91 | 92 | constructor(element) { 93 | super(element.textContent); 94 | this.element = element; 95 | } 96 | 97 | write(chars, exclude) { 98 | this.element.textContent = this.render(chars, exclude); 99 | return this; 100 | } 101 | 102 | } 103 | 104 | // Export a factory function so we don't need 'new'. 105 | export default (element) => new ObfuscatorElement(element); 106 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // Extend one object with another. 2 | export function extend(obj, ext) { 3 | for (let key in ext) { 4 | if (ext.hasOwnProperty(key)) { 5 | obj[key] = ext[key]; 6 | } 7 | } 8 | return obj; 9 | } 10 | 11 | // Transform each character in a string. 12 | export function mapString(str, fn) { 13 | return str.split('').map(fn).join(''); 14 | } 15 | 16 | // Get a random item from an array. 17 | export function sample(arr) { 18 | return arr[Math.floor(Math.random() * arr.length)]; 19 | } 20 | 21 | // Operate on each item in an array. 22 | export function each(arr, fn) { 23 | for (let i=0, l=arr.length; i { 31 | if (!item) return false; 32 | return index; 33 | }).filter(i => i !== false); 34 | } 35 | 36 | // Get an array of elements with a selector, NodeList, Node, or HTMLCollection. 37 | export function getElements(obj) { 38 | if (typeof obj === 'string') 39 | return [].slice.call(document.querySelectorAll(obj)); 40 | if ([NodeList, HTMLCollection].some(collection => obj instanceof collection)) 41 | return [].slice.call(obj); 42 | if (obj.nodeType) 43 | return [obj]; 44 | return obj; 45 | } 46 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'), 2 | package = require('./package'); 3 | 4 | const banner = `${package.name} ${package.version} - ${package.description}\nCopyright (c) ${ new Date().getFullYear() } ${package.author} - ${package.homepage}\nLicense: ${package.license}`; 5 | 6 | module.exports = { 7 | context: __dirname + '/src', 8 | entry: './index.js', 9 | output: { 10 | path: __dirname + '/dist', 11 | filename: `${package.name}.min.js`, 12 | 'library': `${package.name}`, 13 | 'libraryTarget': 'umd' 14 | }, 15 | module: { 16 | loaders: [{ 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loader: 'babel-loader' 20 | }] 21 | }, 22 | plugins: [ 23 | new webpack.BannerPlugin(banner) 24 | ] 25 | }; 26 | --------------------------------------------------------------------------------