diff --git a/.gitignore b/.gitignore index d1c1dbe..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ node_modules -bundle.js diff --git a/bundle.js b/bundle.js new file mode 100644 index 0000000..600cd80 --- /dev/null +++ b/bundle.js @@ -0,0 +1,531 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o wrapNode(node); + +/** + * Import an HTML document into `the-dom` + * + * @param {HTMLDocument} doc Document to import + * @return {Object} A hash with doctype, body, head, html props + */ +exports.html = doc => ({ + create: name => doc.createElement(name), + body: wrapNode(doc.body), + head: wrapNode(doc.head), + html: wrapNode(doc.documentElement), + doctype: wrapNode(doc.doctype) +}); + +},{"./lib/node":3}],2:[function(require,module,exports){ +'use strict'; + +const utils = require('./utils'); + +const split = utils.split; +const iterateArray = utils.iterateArray; + +/** + * Create an object to manipulate given node's CSS classes + * + * @param {HTMLElement} node Input element + * @see `Set` documentation for behaviour information + * @return {Object} Set-like object + */ +const wrapClass = node => { + const res = { + add: function (el) { + if (!this.has(el)) { + const classes = split(node.className); + + classes.push(el); + node.className = classes.join(' '); + } + + return this; + }, + + delete: el => { + const classes = split(node.className), pos = classes.indexOf(el); + + if (pos > -1) { + classes.splice(pos, 1); + node.className = classes.join(' '); + return true; + } + + return false; + }, + + has: el => split(node.className).indexOf(el) !== -1, + clear: () => node.className = '', + + get size() { + return split(node.className).length; + }, + + keys: () => iterateArray(split(node.className)), + values: () => iterateArray(split(node.className)), + entries: () => iterateArray(split(node.className).map(el => [el, el])), + + forEach: function (callback, thisArg) { + for (let cls of this) { + callback.call(thisArg, cls, cls, this); + } + } + }; + + res[Symbol.iterator] = res.values; + return res; +}; + +module.exports = wrapClass; + +},{"./utils":5}],3:[function(require,module,exports){ +'use strict'; + +const wrapClass = require('./class'); +const wrapStyle = require('./style'); +const split = require('./utils').split; + +/** + * Ensure a node is not wrapped before using it in native methods + * + * @param {Node|Object} node A node, wrapped or not + * @return {Node} Unwrapped node + */ +const unwrap = node => + (typeof node !== 'object' || node === null || !node.node) ? node : node.node; + +/** + * Turn a NodeList/HTMLCollection into an array + * for easy manipulation + * + * @param {NodeList|HTMLCollection} list Input collection + * @return {Array} Wrapping array + */ +const wrapList = list => { + const length = list.length; + let result = []; + + for (let i = 0; i < length; i += 1) { + result.push(wrapNode(list.item(i))); + } + + return Object.assign(result, { + on: (evts, handler) => { + result.forEach(node => node.on(evts, handler)); + }, + + off: (evts, handler) => { + result.forEach(node => node.off(evts, handler)); + } + }); +}; + +/** + * Create an object of shortcuts to manipulate + * given node more easily + * + * @param {Node} Input node + * @return {Object} DOM shortcuts + */ +const wrapNode = node => { + if (node === null || typeof node !== 'object') { + return null; + } + + return { + node, + + // search among children + find: query => wrapNode(node.querySelector(query)), + findAll: query => wrapList(node.querySelectorAll(query)), + + // access node's relative tree (parent, children, siblings) + equal: el => unwrap(el) === node, + + get following() { + return wrapNode(node.nextElementSibling); + }, + + get preceding() { + return wrapNode(node.previousElementSibling); + }, + + get parent() { + return wrapNode(node.parentNode); + }, + + get children() { + return wrapList(node.children); + }, + + // check relative positions + precedes: el => !!(unwrap(el).compareDocumentPosition(node) & 2), + follows: el => !!(unwrap(el).compareDocumentPosition(node) & 4), + contains: el => !!(unwrap(el).compareDocumentPosition(node) & 8), + contained: el => !!(unwrap(el).compareDocumentPosition(node) & 16), + + // get and set element attributes + get name() { + return node.tagName.toLowerCase().trim(); + }, + + get type() { + switch (node.nodeType) { + case 1: return 'element'; + case 3: return 'text'; + case 7: return 'processing-instruction'; + case 8: return 'comment'; + case 9: return 'document'; + case 10: return 'document-type'; + case 11: return 'document-fragment'; + default: return null; + } + }, + + getAttr: attr => node.getAttribute(attr), + setAttr: (attr, value) => node.setAttribute(attr, value), + + // place an element in the DOM tree + append: subnode => node.appendChild(unwrap(subnode)), + attach: parent => unwrap(parent).appendChild(node), + remove: child => { + if (child) { + node.removeChild(unwrap(child)); + return; + } + + node.parentNode.removeChild(node); + }, + + // manipulate element's CSS (see wrapClass, wrapStyle) + class: wrapClass(node), + style: wrapStyle(node), + + // change an element's content + get text() { + return node.textContent; + }, + + set text(val) { + node.textContent = val; + }, + + get html() { + return node.innerHTML; + }, + + set html(val) { + node.innerHTML = val; + }, + + // listen to events + on: (evts, handler) => { + split(evts).forEach(evt => { + node.addEventListener(evt, handler); + }); + }, + + off: (evts, handler) => { + split(evts).forEach(evt => { + node.removeEventListener(evt, handler); + }); + } + }; +}; + +module.exports = wrapNode; + +},{"./class":2,"./style":4,"./utils":5}],4:[function(require,module,exports){ +'use strict'; + +const iterateArray = require('./utils').iterateArray; + +/** + * Create an object to manipulate given node's CSS styles + * + * @param {HTMLElement} node Input element + * @see `Map` documentation for behaviour information + * @return {Object} Map-like object + */ +const wrapStyle = node => { + const res = { + set: function (prop, value) { + node.style.setProperty(prop, value); + return this; + }, + + delete: prop => node.style.removeProperty(prop) !== '', + has: prop => [].slice.call(node.style).indexOf(prop) > -1, + + get: prop => { + const result = node.style.getPropertyValue(prop); + + if (result.trim() === '') { + return undefined; + } + + return result; + }, + + clear: () => { + const length = node.style.length; + + for (let i = 0; i < length; i += 1) { + node.style.removeProperty(node.style[i]); + } + }, + + get size() { + return node.style.length; + }, + + keys: () => iterateArray([].slice.call(node.style)), + values: () => iterateArray([].slice.call(node.style).map( + prop => node.style.getPropertyValue(prop))), + entries: () => iterateArray([].slice.call(node.style).map( + prop => [prop, node.style.getPropertyValue(prop)])), + + forEach: function (callback, thisArg) { + for (let cls of this) { + callback.call(thisArg, cls, cls, this); + } + } + }; + + res[Symbol.iterator] = res.values; + return res; +}; + +module.exports = wrapStyle; + +},{"./utils":5}],5:[function(require,module,exports){ +'use strict'; + +const whitespace = /\s+/g; + +/** + * Split a list of whitespace separated tokens, + * excluding empty ones + * + * @param {string} str Input string + * @return {Array} Split tokens + */ +exports.split = str => str.split(whitespace).filter(el => el.trim().length); + +/** + * Create an iterator on an array + * + * @param {Array} arr Array to iterate on + * @return {Object} Iterator for given array + */ +exports.iterateArray = (arr) => { + let next = 0; + + return { + next: () => next < arr.length ? + {value: arr[next++], done: false} : + {done: true} + }; +}; + +},{}],6:[function(require,module,exports){ +'use strict'; + +var _theDom = require('the-dom'); + +var _utils = require('./utils'); + +var _html = (0, _theDom.html)(document); + +var body = _html.body; +var create = _html.create; + +var content = body.find('#content'); +var plotting = body.find('#plotting'); +var ctx = plotting.node.getContext('2d'); + +var padding = 40; // padding between the canvas edges and the points +var image = undefined, + width = undefined, + height = undefined; +var lastUpdate = -Infinity; + +/** + * Create a fractal of given width, height, based on + * a polygon of given amount of vertices, using the + * chaos game applied with given fraction + * + * @param {number} width Fractal width + * @param {number} height Fractal height + * @param {number} fraction Fraction to use + * @param {Array} colors Color of each vertex + * @return {ImageData} Generated pixel data + */ +var chaos = function chaos(width, height, fraction, colors) { + var cx = Math.floor(width / 2); + var cy = Math.floor(height / 2); + var radius = Math.min(cx, cy); + + var count = colors.length; + var vertices = []; + var angleStep = 2 * Math.PI / count; + var initialAngle = undefined; + + // creating 0-width image data will throw an error + if (width <= 0 || height <= 0) { + return ctx.createImageData(1, 1); + } + + var image = ctx.createImageData(width, height); + var data = image.data; + + // we will rotate around an inscribed circle to calculate + // the vertices' positions. We adapt the initial angle so + // that usual polygons look better + if (count === 3) { + initialAngle = -Math.PI / 2; + } else if (count === 4) { + initialAngle = Math.PI / 4; + } else { + initialAngle = 0; + } + + for (var i = 0; i < count; i += 1) { + var current = angleStep * i + initialAngle; + + vertices.push([Math.floor(Math.cos(current) * radius + cx), Math.floor(Math.sin(current) * radius + cy)]); + } + + // now we apply the chaos algorithm: + // for any point, the next point is a `fraction` of the + // distance between it and a random vertex + var point = vertices[0]; + var iterations = 200000; + var drop = 1000; + + while (iterations--) { + var vertexNumber = (0, _utils.randomNumber)(0, count); + var vertex = vertices[vertexNumber], + color = colors[vertexNumber]; + + point = [Math.floor((point[0] - vertex[0]) * fraction + vertex[0]), Math.floor((point[1] - vertex[1]) * fraction + vertex[1])]; + + // skip the first 1000 points + if (drop === 0) { + var i = (point[1] * width + point[0]) * 4; + + data[i] = color[0]; + data[i + 1] = color[1]; + data[i + 2] = color[2]; + data[i + 3] = 255; + } else { + drop--; + } + } + + return image; +}; + +/** + * Render the scene, recalculating the points + * positions if they need to + * + * @return {null} + */ +var render = function render() { + // only recalculate every 16.67 ms + if (+new Date() - lastUpdate > 16.67) { + image = chaos(width - 2 * padding, height - 2 * padding, 1 / 2, [[255, 0, 0], [0, 255, 0], [0, 0, 255]]); + + lastUpdate = +new Date(); + } + + ctx.clearRect(0, 0, width, height); + ctx.putImageData(image, padding, padding); +}; + +/** + * Resize the canvas to fit the new + * window size and redraw the scene + * + * @return {null} + */ +var resize = function resize() { + width = content.node.clientWidth; + height = content.node.clientHeight; + + plotting.setAttr('width', width); + plotting.setAttr('height', height); + + render(); +}; + +window.onresize = resize; +resize(); + +},{"./utils":7,"the-dom":1}],7:[function(require,module,exports){ +'use strict' + +/** + * Get a random whole number + * + * @param {number} min Minimal value for the number + * @param {number} max Maximal value for the number (excluded) + * @return {number} Random number + */ +; +Object.defineProperty(exports, "__esModule", { + value: true +}); +var randomNumber = exports.randomNumber = function randomNumber(min, max) { + return Math.floor(Math.random() * (max - min)) + min; +}; + +/** + * Generate a random color + * + * @return {Array} RGB components + */ +var randomColor = exports.randomColor = function randomColor() { + var color = []; + + for (var i = 0; i < 3; i++) { + color.push(Math.round(Math.random().toFixed(2) * 255)); + } + + return color; +}; + +/** + * Convert a decimal number to its hexadecimal representation + * + * @param {number} input Number to be converted + * @return {string} Number representation + */ +var hex = function hex(input) { + var hex = parseInt(input, 10).toString(16); + return hex.length === 1 ? '0' + hex : hex; +}; + +/** + * Convert a RGB color to its hexadecimal representation + * + * @param {Array} color RGB color + * @return {string} Hex representation + */ +var rgbToHex = exports.rgbToHex = function rgbToHex(color) { + return '#' + hex(color[0]) + hex(color[1]) + hex(color[2]); +}; + +},{}]},{},[6]);