├── .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 | >
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 |
--------------------------------------------------------------------------------