├── README.md ├── demo ├── class-examples.js ├── clean-class-object.js ├── control-values.js ├── controls-listeners.js ├── custom-event-listener.js ├── demo-dimensions.js ├── demo.css ├── desktop-examples.js ├── mobile-demo.js └── mobile-examples.js ├── images ├── dot-matrix-logo-big.png ├── dot-matrix-logo.png └── dot-matrix-og-image.jpg ├── index.html └── src ├── BaseDot.js ├── CoordinateTranslator.js ├── DotMatrix.js ├── LetterDot.js └── SmartDot.js /README.md: -------------------------------------------------------------------------------- 1 | # DotMatrx.js 2 | ![DotMatrixLogo](https://github.com/weareenvoy/dot-matrix/blob/master/images/dot-matrix-logo-big.png) 3 | DotMatrix.js is a small, performant class-based library. DotMatrix utilizes SVG(s) instead of canvas for performance and development reasons. SVG(s), CSS3 transitions and JavaScript were used to create the beautiful animations. 4 | 5 | ### Playground 6 | [Configurable Demo](https://mmason33.github.io/dot-matrix/) 7 | **Note: LetterDot is not available in the current demo** 8 | 9 | ### Configurations 10 | 11 | `new DotMatrix(rootSVG, args);` 12 | 13 | ##### rootSVG 14 | | property | type | default | optional | 15 | | ----------------- |:-----------------:| -----: | ----------:| 16 | | rootSvg | node | undefined | no | 17 | 18 | ##### args 19 | | property | type | default | optional | 20 | | ----------------- | ----------------- | ----- | ---------- | 21 | | height | number | undefined | yes | 22 | | width | number | undefined | yes | 23 | | columns | number | 40 | yes | 24 | | rows | number | 40 | yes | 25 | | animationDelay | number/boolean | 500 | yes | 26 | | distanceToFear | nunber | 50 | yes | 27 | | distanceToStep | number | 10 | yes | 28 | | dotColorPattern | string | random - options: random, diagonal, vertical, horizontal, fill | yes | 29 | | dotFillColor | string | black - options: hex color, color literal | yes | 30 | | dotRadius | number | 5 | yes | 31 | | dotType | string | smart - options: smart, letter | yes | 32 | | padding | number | 30 | yes | 33 | | patternColors | array | ['red','orange','yellow','green','cyan','skyblue','blue','indigo','violet','grey']| yes | 34 | | spacing| number| 30| yes| 35 | | svgBackgroundColor| string| black - options: hex color, color literal | yes| 36 | | cssClassGoingHome| string| animate_going_home| yes| 37 | | timing| object| {fromHome: 'ease',backHome: 'ease'}| yes| 38 | | duration| object| {fromHome: '0.1s',backHome: '1s'}| yes| 39 | 40 | ##### Example 41 | ```javascript 42 | 43 | // Entry point SVG 44 | const svg = document.querySelector('.some-svg'); 45 | 46 | // Full args 47 | new DotMatrix( 48 | svg, 49 | { 50 | padding: 50, 51 | spacing: 80, 52 | width: 50, 53 | height: 50, 54 | distanceToFear: 100, 55 | distanceToStep: 10, 56 | animationDelay: 200, 57 | dotRadius: 10, 58 | dotType: 'smart', 59 | dotFillColor: 'green', 60 | letterFillColor: 'white', 61 | dotColorPattern: 'diagonal', 62 | patternColors: [ 63 | 'red', 64 | 'orange', 65 | 'yellow', 66 | 'green', 67 | 'cyan', 68 | 'skyblue', 69 | 'blue', 70 | 'indigo', 71 | 'violet', 72 | 'lightgray', 73 | ], 74 | cssClassGoingHome: 'animate_going_home', 75 | timing: { 76 | fromHome: 'ease', 77 | backHome: 'ease', 78 | }, 79 | duration: { 80 | fromHome: '0.1s', 81 | backHome: '1s', 82 | }, 83 | } 84 | ); 85 | 86 | // Height and Width => columns and rows dynamically calculated 87 | new DotMatrix( 88 | svg, 89 | { 90 | height: 500, 91 | width: 500, 92 | } 93 | ); 94 | 95 | // Columns and Rows => height and width dynamically calculated 96 | new DotMatrix( 97 | svg, 98 | { 99 | columns: 30, 100 | rows: 10, 101 | } 102 | ); 103 | 104 | // Height and Width take precendent 105 | new DotMatrix( 106 | svg, 107 | { 108 | height: 500, 109 | width: 500, 110 | columns: 30, 111 | rows: 10, 112 | } 113 | ); 114 | 115 | ``` 116 | 117 | **LetterDot specific properties** 118 | | property | type | default | optional | 119 | | ----------------- |:-----------------:| -----: | ----------:| 120 | | letterFillColor | string | white | yes | 121 | | wordsList | array | undefined | no | 122 | 123 | 124 | **Note: At this time dot classes are not intended to be instantiated outside of the DotMatrix class.** 125 | 126 | `new BaseDot(rootSVG, args);` 127 | | property | type | default | optional | 128 | | ----------------- |:-----------------:| -----: | ----------:| 129 | | dotFillColor | string | black | yes | 130 | | dotRadius | number | 5 | yes | 131 | | animationDelay | number/boolean | 500 | yes | 132 | | distanceToFear | nunber | 50 | yes | 133 | | uniqueIdentifier | number/iterator | undefined | no | 134 | | isDesktop | boolean | undefined | no | 135 | 136 | 137 | **Extends BaseDot** 138 | 139 | `new SmartDot(rootSVG, args);` 140 | | property | type | default | optional | 141 | | ----------------- |:-----------------:| -----: | ----------:| 142 | | distanceToStep | nunber | 50 | yes | 143 | | cssClassGoingHome | string | animate_going_home | yes| 144 | 145 | `new LetterDot(rootSVG, args);` 146 | | property | type | default | optional | 147 | | ----------------- |:-----------------:| -----: | ----------:| 148 | | dotLetter | string | undefined | no | 149 | | letterFillColor | string | white | yes| 150 | 151 | ### Sizing 152 | Without passing explicit arguments, the DotMatrx will default to **40 columns and 40 rows**. When columns and rows are passed the height and width of the matrix is dynamically calculated. When height and width dimension are passed, columns and rows will be dynamically calculated. -------------------------------------------------------------------------------- /demo/class-examples.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------- 2 | // Class with args example 3 | //--------------------------------------------------- 4 | 5 | const svg = document.querySelector('.some-svg'); 6 | 7 | // Smart Dot 8 | new DotMatrix( 9 | svg, 10 | { 11 | height: 'number', 12 | width: 'number', 13 | rows: 'number', 14 | columns: 'number', 15 | animationDelay: 'number', 16 | distanceToFear: 'number', 17 | distanceToStep: 'number', 18 | dotColorPattern: 'string', 19 | dotFillColor: 'string', 20 | dotRadius: 'number', 21 | dotType: 'string', 22 | padding: 'number', 23 | patternColors: 'array', 24 | spacing: 'number', 25 | svgBackgroundColor: 'string', 26 | cssClassGoingHome: 'string', 27 | timing: { 28 | fromHome: 'string', 29 | backHome: 'string', 30 | }, 31 | duration: { 32 | fromHome: 'string', 33 | backHome: 'string', 34 | }, 35 | } 36 | ); 37 | 38 | // Letter Dot 39 | new DotMatrix( 40 | svg, 41 | { 42 | height: 'number', 43 | width: 'number', 44 | rows: 'number', 45 | columns: 'number', 46 | animationDelay: 'number', 47 | distanceToFear: 'number', 48 | dotColorPattern: 'string', 49 | dotFillColor: 'string', 50 | dotRadius: 'number', 51 | dotType: 'string', 52 | padding: 'number', 53 | patternColors: 'array', 54 | spacing: 'number', 55 | svgBackgroundColor: 'string', 56 | // letter specific props 57 | letterFillColor: 'string', 58 | wordsList: 'array', 59 | } 60 | ); 61 | 62 | // Base Dot 63 | new BaseDot( 64 | svg, 65 | { 66 | dotRadius: 'number', 67 | dotFillColor: 'string', 68 | animationDelay: 'number', 69 | distanceToFear: 'number', 70 | uniqueIdentifier: 'loop iterator', 71 | isDesktop: 'boolean', 72 | } 73 | ); 74 | 75 | // Extends BaseDot 76 | // ----------------------------------- 77 | new SmartDot( 78 | svg, 79 | { 80 | distanceToStep: 'number', 81 | cssClassGoingHome: 'string', 82 | } 83 | ); 84 | 85 | new LetterDot( 86 | svg, 87 | { 88 | dotLetter: 'string', 89 | letterFillColor: 'string', 90 | } 91 | ); -------------------------------------------------------------------------------- /demo/clean-class-object.js: -------------------------------------------------------------------------------- 1 | function cleanObjectClass(matrix) { 2 | let cleanedMatrix = {}; 3 | Object.keys(matrix).forEach(item => { 4 | cleanedMatrix[item] = matrix[item]; 5 | }); 6 | 7 | cleanedMatrix.rows = Math.round(cleanedMatrix.rows); 8 | cleanedMatrix.columns = Math.round(cleanedMatrix.columns); 9 | delete cleanedMatrix.wordsList; 10 | delete cleanedMatrix.letterFillColor; 11 | delete cleanedMatrix.args; 12 | delete cleanedMatrix.rootSvg; 13 | delete cleanedMatrix.colPaddingAdjust; 14 | delete cleanedMatrix.rowPaddingAdjust; 15 | delete cleanedMatrix.isDesktop; 16 | delete cleanedMatrix.calculated; 17 | 18 | return cleanedMatrix; 19 | } -------------------------------------------------------------------------------- /demo/control-values.js: -------------------------------------------------------------------------------- 1 | const spacing = document.querySelector('input[name="spacing"]'); 2 | const padding = document.querySelector('input[name="padding"]'); 3 | const fear = document.querySelector('input[name="fear-distance"]'); 4 | const delay = document.querySelector('input[name="delay"]'); 5 | const step = document.querySelector('input[name="step-distance"]'); 6 | const dotRadius = document.querySelector('input[name="dotRadius"]'); 7 | const dotFillColor = document.querySelector('input[name="dotFillColor"]'); 8 | const backgroundColor = document.querySelector('input[name="backgroundColor"]'); 9 | 10 | function getControlValues() { 11 | const updated_spacing = parseInt(spacing.value); 12 | const updated_padding = parseInt(padding.value); 13 | const updated_fear = parseInt(fear.value); 14 | const updated_delay = parseInt(delay.value); 15 | const updated_step = parseInt(step.value); 16 | const updated_dotRadius = parseInt(dotRadius.value); 17 | const updated_dotFillColor = dotFillColor.value || false; 18 | 19 | const vals = { 20 | spacing: updated_spacing, 21 | padding: updated_padding, 22 | distanceToFear: updated_fear, 23 | animationDelay: updated_delay, 24 | distanceToStep: updated_step, 25 | dotRadius: updated_dotRadius, 26 | dotFillColor: updated_dotFillColor, 27 | svgBackgroundColor: backgroundColor.value || false, 28 | }; 29 | 30 | return vals; 31 | } 32 | 33 | function setControlValues(cleanMatrixObject) { 34 | spacing.setAttribute('value', cleanMatrixObject.spacing); 35 | padding.setAttribute('value', cleanMatrixObject.padding); 36 | fear.setAttribute('value', cleanMatrixObject.distanceToFear); 37 | delay.setAttribute('value', cleanMatrixObject.animationDelay); 38 | step.setAttribute('value', cleanMatrixObject.distanceToStep); 39 | dotRadius.setAttribute('value', cleanMatrixObject.dotRadius); 40 | backgroundColor.setAttribute('value', cleanMatrixObject.svgBackgroundColor); 41 | } -------------------------------------------------------------------------------- /demo/controls-listeners.js: -------------------------------------------------------------------------------- 1 | function controlsListeners() { 2 | let timeout; 3 | Array.from(document.querySelectorAll('input')).forEach(input => { 4 | if (input.type === 'range') { 5 | input.addEventListener('change', () => { 6 | document.dispatchEvent(new Event('matrix_reload')); 7 | }); 8 | return false; 9 | } 10 | 11 | input.addEventListener('keyup', () => { 12 | if (timeout) clearTimeout(timeout); 13 | timeout = setTimeout(() => { 14 | document.dispatchEvent(new Event('matrix_reload')); 15 | }, 250) 16 | }); 17 | }); 18 | 19 | Array.from(document.querySelectorAll('button')).forEach(button => { 20 | button.addEventListener('click', () => { 21 | document.dispatchEvent(new CustomEvent('matrix_reload', { 22 | detail: { 23 | config: button.classList.value, 24 | } 25 | })); 26 | }); 27 | }); 28 | } -------------------------------------------------------------------------------- /demo/custom-event-listener.js: -------------------------------------------------------------------------------- 1 | function matrixReloadEvent(wrapper, config, count) { 2 | document.addEventListener('matrix_reload', (e) => { 3 | // Performance 4 | e.preventDefault(); 5 | e.stopPropagation(); 6 | 7 | // Vars from controls 8 | const values = getControlValues(); 9 | 10 | wrapper.style.width = `${window.adjustedWidth}px`; 11 | wrapper.style.height = `${window.innerHeight}px`; 12 | 13 | // Old svg 14 | const svg = document.querySelector('.dot-matrix'); 15 | // Remove old svg 16 | svg.remove(); 17 | 18 | // Create new svg, add class and append to wrapper 19 | const newSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 20 | newSvg.classList = 'dot-matrix'; 21 | wrapper.appendChild(newSvg); 22 | 23 | // Delete old class 24 | delete window.matrix 25 | // New Config object 26 | const configObject = { 27 | width: window.adjustedWidth, 28 | height: window.innerHeight, 29 | padding: values.padding, 30 | spacing: values.spacing, 31 | dotRadius: values.dotRadius, 32 | dotFillColor: values.dotFillColor, 33 | dotColorPattern: !!values.dotFillColor ? 'fill' : 'diagonal', 34 | distanceToFear: values.distanceToFear, 35 | distanceToStep: values.distanceToStep, 36 | animationDelay: !!values.animationDelay === false ? false : values.animationDelay, 37 | cssClassGoingHome: 'animate_going_home', 38 | svgBackgroundColor: values.svgBackgroundColor || 'black', 39 | timing: { 40 | fromHome: 'ease', 41 | backHome: 'ease', 42 | }, 43 | duration: { 44 | fromHome: '0.1s', 45 | backHome: '1s', 46 | }, 47 | }; 48 | 49 | // Kick it off 50 | window.matrix = new DotMatrix( 51 | newSvg, 52 | e.detail ? getExampleConfig(e.detail.config, window.adjustedWidth) : configObject, 53 | ); 54 | 55 | // Clean object 56 | const updatedCleanedObject = cleanObjectClass(window.matrix); 57 | 58 | // Update control values 59 | setControlValues(updatedCleanedObject); 60 | 61 | // Print config json and update dot count 62 | config.innerHTML = JSON.stringify(updatedCleanedObject, undefined, 2); 63 | count.innerHTML = parseInt(updatedCleanedObject.rows) * parseInt(updatedCleanedObject.columns); 64 | }); 65 | } -------------------------------------------------------------------------------- /demo/demo-dimensions.js: -------------------------------------------------------------------------------- 1 | function demoDimensions() { 2 | const controls = document.querySelector('.controls'); 3 | const adjustedWidth = window.innerWidth - controls.clientWidth; 4 | return adjustedWidth; 5 | } -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | font-family: "Cutive Mono", monospace; 7 | position: relative; 8 | overflow-x: hidden !important; 9 | background-color: black; 10 | } 11 | 12 | .github-link { 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | z-index: 99999; 17 | padding: 10px; 18 | } 19 | 20 | .github-link a { 21 | color: white; 22 | } 23 | 24 | .wrapper { 25 | position: fixed; 26 | box-sizing: border-box; 27 | } 28 | 29 | svg { 30 | display: inline-block !important; 31 | position: relative; 32 | } 33 | 34 | .controls { 35 | position: absolute; 36 | top: 0; 37 | right: 0; 38 | padding: 5px 15px; 39 | z-index: 9999; 40 | background-color: white; 41 | text-align: left; 42 | overflow: scroll; 43 | width: 320px; 44 | box-sizing: border-box; 45 | background-color: black; 46 | } 47 | 48 | .config { 49 | max-width: 320px; 50 | overflow-x: scroll; 51 | } 52 | 53 | label { 54 | display: block; 55 | text-transform: uppercase; 56 | font-size: 14px; 57 | margin: 0 0 5px; 58 | color: rgb(192, 192, 192); 59 | } 60 | 61 | p, 62 | pre { 63 | color: rgb(192, 192, 192); 64 | } 65 | 66 | input { 67 | margin-left: 5px; 68 | opacity: .85 !important; 69 | } 70 | 71 | button { 72 | display: block; 73 | margin: 0 0 5px; 74 | background-color: transparent; 75 | color: rgb(192, 192, 192); 76 | border-color: rgb(192, 192, 192); 77 | padding: 3px 8px; 78 | } 79 | 80 | h1 { 81 | position: absolute; 82 | top: 50%; 83 | left: 50%; 84 | transform: translate(-50%, -50%); 85 | color: white; 86 | z-index: 99999; 87 | pointer-events: none; 88 | opacity: .35; 89 | } 90 | 91 | .mobile-controls, 92 | .mobile-trigger, 93 | .trigger { 94 | display: none; 95 | } 96 | 97 | @media screen and (max-width: 991px) { 98 | 99 | h1 { 100 | font-size: 25px; 101 | } 102 | 103 | .github-link { 104 | display: none; 105 | } 106 | 107 | .mobile-controls { 108 | display: flex; 109 | flex-wrap: wrap; 110 | position: absolute; 111 | z-index: 9999; 112 | width: 100%; 113 | height: 100%; 114 | text-align: center; 115 | background-color: black; 116 | opacity: 0; 117 | pointer-events: none; 118 | transition: ease opacity .5s; 119 | } 120 | 121 | .mobile-controls.show { 122 | opacity: 1; 123 | pointer-events: all; 124 | } 125 | 126 | .mobile-controls button { 127 | font-size: 7vw; 128 | font-family: "Cutive Mono", monospace; 129 | margin: 20px auto; 130 | flex: 0 0 90%; 131 | } 132 | 133 | .mobile-trigger { 134 | display: block; 135 | position: absolute; 136 | z-index: 999999; 137 | height: 50px; 138 | width: 50px; 139 | opacity: 1; 140 | transition: ease opacity .3s; 141 | } 142 | 143 | .mobile-trigger.hide { 144 | opacity: 0; 145 | pointer-events: none; 146 | } 147 | 148 | .svg-trigger { 149 | height: 50px; 150 | width: 50px; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /demo/desktop-examples.js: -------------------------------------------------------------------------------- 1 | function getExampleConfig(configName, width) { 2 | window[configName].width = width; 3 | return window[configName]; 4 | } 5 | 6 | window.moderate_dots_spacing = { 7 | padding: 50, 8 | spacing: 50, 9 | width: window.adjustedWidth, 10 | height: window.innerHeight, 11 | svgBackgroundColor: 'black', 12 | distanceToFear: 100, 13 | distanceToStep: 30, 14 | animationDelay: 200, 15 | dotRadius: 5, 16 | dotColorPattern: 'vertical', 17 | patternColors: [ 18 | 'red', 19 | 'orange', 20 | 'yellow', 21 | 'green', 22 | 'cyan', 23 | 'skyblue', 24 | 'blue', 25 | 'indigo', 26 | 'violet', 27 | 'lightgrey', 28 | ], 29 | cssClassGoingHome: 'animate_going_home', 30 | transitions: { 31 | timing: { 32 | fromHome: 'ease', 33 | backHome: 'ease', 34 | }, 35 | duration: { 36 | fromHome: '0.1s', 37 | backHome: '1s', 38 | }, 39 | }, 40 | }; 41 | 42 | window.tenK_smalls_dots = { 43 | padding: 10, 44 | spacing: 10, 45 | width: window.adjustedWidth, 46 | height: window.innerHeight, 47 | svgBackgroundColor: 'black', 48 | distanceToFear: 100, 49 | distanceToStep: 30, 50 | animationDelay: 200, 51 | dotRadius: 2, 52 | dotColorPattern: 'random', 53 | patternColors: [ 54 | 'red', 55 | 'orange', 56 | 'yellow', 57 | 'green', 58 | 'cyan', 59 | 'skyblue', 60 | 'blue', 61 | 'indigo', 62 | 'violet', 63 | 'lightgrey', 64 | ], 65 | cssClassGoingHome: 'animate_going_home', 66 | transitions: { 67 | timing: { 68 | fromHome: 'ease', 69 | backHome: 'ease', 70 | }, 71 | duration: { 72 | fromHome: '0.1s', 73 | backHome: '1s', 74 | }, 75 | }, 76 | }; 77 | 78 | window.spaced_large_dots = { 79 | padding: 50, 80 | spacing: 120, 81 | width: window.adjustedWidth, 82 | height: window.innerHeight, 83 | svgBackgroundColor: 'black', 84 | distanceToFear: 100, 85 | distanceToStep: 30, 86 | animationDelay: 200, 87 | dotRadius: 50, 88 | dotColorPattern: 'random', 89 | patternColors: [ 90 | 'red', 91 | 'orange', 92 | 'yellow', 93 | 'green', 94 | 'cyan', 95 | 'skyblue', 96 | 'blue', 97 | 'indigo', 98 | 'violet', 99 | 'lightgrey', 100 | ], 101 | cssClassGoingHome: 'animate_going_home', 102 | transitions: { 103 | timing: { 104 | fromHome: 'ease', 105 | backHome: 'ease', 106 | }, 107 | duration: { 108 | fromHome: '0.1s', 109 | backHome: '1s', 110 | }, 111 | }, 112 | }; 113 | 114 | window.spaced_medium_dots = { 115 | padding: 50, 116 | spacing: 50, 117 | width: window.adjustedWidth, 118 | height: window.innerHeight, 119 | svgBackgroundColor: 'black', 120 | distanceToFear: 50, 121 | distanceToStep: 30, 122 | animationDelay: 200, 123 | dotRadius: 20, 124 | dotColorPattern: 'horizontal', 125 | patternColors: [ 126 | 'red', 127 | 'orange', 128 | 'yellow', 129 | 'green', 130 | 'cyan', 131 | 'skyblue', 132 | 'blue', 133 | 'indigo', 134 | 'violet', 135 | 'lightgrey', 136 | ], 137 | cssClassGoingHome: 'animate_going_home', 138 | transitions: { 139 | timing: { 140 | fromHome: 'ease', 141 | backHome: 'ease', 142 | }, 143 | duration: { 144 | fromHome: '0.1s', 145 | backHome: '1s', 146 | }, 147 | }, 148 | } 149 | 150 | window.dense_group = { 151 | padding: 50, 152 | spacing: 20, 153 | width: window.adjustedWidth, 154 | height: window.innerHeight, 155 | svgBackgroundColor: 'black', 156 | distanceToFear: 25, 157 | distanceToStep: 10, 158 | animationDelay: 200, 159 | dotRadius: 20, 160 | dotColorPattern: 'diagonal', 161 | patternColors: [ 162 | 'red', 163 | 'orange', 164 | 'yellow', 165 | 'green', 166 | 'cyan', 167 | 'skyblue', 168 | 'blue', 169 | 'indigo', 170 | 'violet', 171 | 'lightgrey', 172 | ], 173 | cssClassGoingHome: 'animate_going_home', 174 | transitions: { 175 | timing: { 176 | fromHome: 'ease', 177 | backHome: 'ease', 178 | }, 179 | duration: { 180 | fromHome: '0.1s', 181 | backHome: '1s', 182 | }, 183 | }, 184 | }; 185 | -------------------------------------------------------------------------------- /demo/mobile-demo.js: -------------------------------------------------------------------------------- 1 | function mobileDemo({ 2 | svg, 3 | controls, 4 | wrapper, 5 | }) { 6 | // Clean up 7 | controls.remove(); 8 | wrapper.style.margin = 0; 9 | 10 | // Matrix Config 11 | let config = { 12 | padding: 15, 13 | spacing: 20, 14 | height: window.innerHeight, 15 | width: window.innerWidth, 16 | svgBackgroundColor: 'black', 17 | dotRadius: 6, 18 | dotFillColor: 'black', 19 | dotColorPattern: 'diagonal', 20 | distanceToFear: 30, 21 | distanceToStep: 15, 22 | animationDelay: 300, 23 | cssClassGoingHome: 'animate_going_home', 24 | timing: { 25 | fromHome: 'ease', 26 | backHome: 'ease' 27 | }, 28 | duration: { 29 | fromHome: '0.1s', 30 | backHome: '1s' 31 | } 32 | }; 33 | 34 | const mobileTrigger = document.querySelector('.mobile-trigger'); 35 | const mobileControls = document.querySelector('.mobile-controls'); 36 | 37 | // Setting menu 38 | mobileTrigger.addEventListener('touchstart', (e) => { 39 | mobileControls.classList.contains('show') ? 40 | mobileControls.classList.remove('show') : 41 | mobileControls.classList.add('show'); 42 | }); 43 | 44 | mobileControls.addEventListener('touchstart', (e) => { 45 | let config; 46 | const className = e.target.classList.value; 47 | 48 | switch(className) { 49 | case 'mobile_small_dots': 50 | config = window[className]; 51 | break; 52 | case 'mobile_medium_dots': 53 | config = window[className]; 54 | break; 55 | case 'mobile_large_dots': 56 | config = window[className]; 57 | break; 58 | } 59 | 60 | // Remove old svg 61 | let svg = document.querySelector('.dot-matrix'); 62 | svg.remove(); 63 | 64 | // Create new svg, add class and append to wrapper 65 | const newSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 66 | newSvg.classList = 'dot-matrix'; 67 | wrapper.appendChild(newSvg); 68 | 69 | // Set new listeners 70 | setTouchMoveHideSettings(newSvg); 71 | 72 | new DotMatrix( 73 | newSvg, 74 | config 75 | ); 76 | 77 | mobileControls.classList.remove('show'); 78 | }); 79 | 80 | // Set controls listeners 81 | setTouchMoveHideSettings(svg); 82 | 83 | // Initial 84 | new DotMatrix( 85 | svg, 86 | config 87 | ); 88 | } 89 | 90 | function setTouchMoveHideSettings(svg) { 91 | const mobileTrigger = document.querySelector('.mobile-trigger'); 92 | 93 | svg.addEventListener('touchmove', (e) => { 94 | e.preventDefault(); 95 | e.stopPropagation(); 96 | // mobileTrigger.classList.contains('hide') ? 97 | // mobileTrigger.classList.remove('hide') : 98 | mobileTrigger.classList.add('hide'); 99 | }); 100 | 101 | svg.addEventListener('touchend', (e) => { 102 | e.preventDefault(); 103 | e.stopPropagation(); 104 | // mobileTrigger.classList.contains('hide') ? 105 | // mobileTrigger.classList.remove('hide') : 106 | mobileTrigger.classList.remove('hide'); 107 | }); 108 | } -------------------------------------------------------------------------------- /demo/mobile-examples.js: -------------------------------------------------------------------------------- 1 | window.mobile_small_dots = { 2 | padding: 15, 3 | spacing: 20, 4 | height: window.innerHeight, 5 | width: window.innerWidth, 6 | svgBackgroundColor: 'black', 7 | dotRadius: 6, 8 | dotFillColor: 'black', 9 | dotColorPattern: 'diagonal', 10 | distanceToFear: 30, 11 | distanceToStep: 15, 12 | animationDelay: 300, 13 | cssClassGoingHome: 'animate_going_home', 14 | timing: { 15 | fromHome: 'ease', 16 | backHome: 'ease' 17 | }, 18 | duration: { 19 | fromHome: '0.1s', 20 | backHome: '1s' 21 | } 22 | }; 23 | 24 | window.mobile_medium_dots = { 25 | padding: 30, 26 | spacing: 50, 27 | height: window.innerHeight, 28 | width: window.innerWidth, 29 | svgBackgroundColor: 'black', 30 | dotRadius: 20, 31 | dotFillColor: 'black', 32 | dotColorPattern: 'vertical', 33 | distanceToFear: 30, 34 | distanceToStep: 40, 35 | animationDelay: 300, 36 | cssClassGoingHome: 'animate_going_home', 37 | timing: { 38 | fromHome: 'ease', 39 | backHome: 'ease' 40 | }, 41 | duration: { 42 | fromHome: '0.1s', 43 | backHome: '1s' 44 | } 45 | }; 46 | 47 | window.mobile_large_dots = { 48 | padding: 40, 49 | spacing: 90, 50 | height: window.innerHeight, 51 | width: window.innerWidth, 52 | svgBackgroundColor: 'black', 53 | dotRadius: 40, 54 | dotFillColor: 'black', 55 | dotColorPattern: 'random', 56 | distanceToFear: 60, 57 | distanceToStep: 80, 58 | animationDelay: 300, 59 | cssClassGoingHome: 'animate_going_home', 60 | timing: { 61 | fromHome: 'ease', 62 | backHome: 'ease' 63 | }, 64 | duration: { 65 | fromHome: '0.1s', 66 | backHome: '1s' 67 | } 68 | }; -------------------------------------------------------------------------------- /images/dot-matrix-logo-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmason33/dot-matrix/32ac0c33c72a8ff7353d1330b78d8dab26c31589/images/dot-matrix-logo-big.png -------------------------------------------------------------------------------- /images/dot-matrix-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmason33/dot-matrix/32ac0c33c72a8ff7353d1330b78d8dab26c31589/images/dot-matrix-logo.png -------------------------------------------------------------------------------- /images/dot-matrix-og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmason33/dot-matrix/32ac0c33c72a8ff7353d1330b78d8dab26c31589/images/dot-matrix-og-image.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Dot Matrix🔵 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 |

Dot Count:

28 | 29 | 32 | 33 | 36 | 37 | 40 | 41 | 44 | 45 | 48 | 49 | 52 | 53 | 56 | 57 | 60 |
61 |

Example

62 | 63 | 64 | 65 | 66 | 67 |
68 |

Config Object(In JSON)

69 |

 70 |         
71 |
72 |

Dot Matrix

73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 192 | 193 | -------------------------------------------------------------------------------- /src/BaseDot.js: -------------------------------------------------------------------------------- 1 | class BaseDot { 2 | constructor(svg, args) { 3 | this.svg = svg; 4 | this.dotRadius = args.dotRadius || 5; 5 | this.dotFillColor = args.dotFillColor || 'black'; 6 | this.distanceToFear = args.distanceToFear || 50; 7 | this.animationDelay = args.animationDelay || 500; 8 | this.uniqueIdentifier = args.uniqueIdentifier; 9 | this.isDesktop = args.isDesktop; 10 | this.eventType = this.isDesktop ? 'mousemove' : 'touchmove'; 11 | 12 | // Beginning home coordinates => Object passed by reference so the property values need to referenced and retrieved 13 | this.coordinates = { 14 | home: { 15 | x: args.homeCoordinate.x, 16 | y: args.homeCoordinate.y, 17 | }, 18 | current: { 19 | x: args.homeCoordinate.x, 20 | y: args.homeCoordinate.y, 21 | }, 22 | }; 23 | 24 | this.insertOnDOM(); 25 | this.addEventListeners(); 26 | this.onMouseLeave(); 27 | } 28 | 29 | insertOnDOM() { 30 | const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); 31 | /** 32 | * RECTS ARE POSSIBLE - TODO: ALLOW RECTANGLES 33 | * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect 34 | */ 35 | circle.classList.add('ease-in'); 36 | circle.setAttribute('cx', this.coordinates.home.x); 37 | circle.setAttribute('cy', this.coordinates.home.y); 38 | circle.setAttribute('r', this.dotRadius); 39 | circle.setAttribute('fill', this.dotFillColor); 40 | circle.setAttribute('style', 'transform: translate(0px, 0px)'); 41 | this.dot = circle; 42 | this.svg.appendChild(this.dot); 43 | } 44 | 45 | addEventListeners() { 46 | const methodName = `delegate_${this.eventType}`; 47 | this.svg.addEventListener(this.eventType, this[methodName].bind(this), false); 48 | } 49 | 50 | respondToInput(coordinate, callbackObject) { 51 | // Static method 52 | this.vector_from_mouse_to_me = (new CoordinateTranslator).setCenterCoordinate(coordinate).getVectorToPoint(this.coordinates.current); 53 | 54 | if (!this.isMouseNearMe(this.vector_from_mouse_to_me)) { 55 | callbackObject.baseState(); 56 | return false; 57 | } 58 | 59 | callbackObject.alteredState(); 60 | } 61 | 62 | isMouseNearMe(vector_from_mouse_to_me) { 63 | if (vector_from_mouse_to_me.radius < this.distanceToFear) { 64 | return true; 65 | } 66 | return false; 67 | } 68 | 69 | onMouseLeave() { 70 | this.svg.addEventListener('mouseleave', () => { 71 | this.baseState(); 72 | }); 73 | } 74 | } -------------------------------------------------------------------------------- /src/CoordinateTranslator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class CoordinateTranslator - Translate rise/run (Cartesian Coordinates) to another format 3 | * Coordinate conversion explained https://www.mathsisfun.com/polar-cartesian-coordinates.html 4 | */ 5 | class CoordinateTranslator { 6 | constructor() { 7 | // Default 8 | this.center_coordinate = { 9 | x: 0, 10 | y: 0, 11 | z: 0, 12 | }; 13 | } 14 | 15 | /** 16 | * @param {Object} center_coordinate 17 | * @param {Int} center_coordinate.x 18 | * @param {Int} center_coordinate.y 19 | * @param {Int} center_coordinate.z 20 | * @return {Object} this 21 | */ 22 | setCenterCoordinate(coordinate) { 23 | if (coordinate.x) this.center_coordinate.x = coordinate.x; 24 | if (coordinate.y) this.center_coordinate.y = coordinate.y; 25 | if (coordinate.z) this.center_coordinate.z = coordinate.z; 26 | return this; 27 | } 28 | 29 | /** 30 | * @param {Int} radius 31 | * @param {Int} angle_in_degrees 32 | * @return {Object} {x:?,y:?,z:?} A 2D Cartesian Coordinate 33 | */ 34 | fromPolar(radius, angle_in_degrees) { 35 | const angle_in_radians = angle_in_degrees * Math.PI / 180.0; 36 | 37 | return { 38 | x: +this.center_coordinate.x + +radius * Math.cos(angle_in_radians), 39 | y: +this.center_coordinate.y + +radius * Math.sin(angle_in_radians), 40 | }; 41 | } 42 | 43 | /** 44 | * @param {Int} radius 45 | * @param {Int} angle_in_degrees 46 | * @param {Int} azimuth_in_degrees 47 | * @return {Object} {x:?,y:?,z:?} A 3D Cartesian Coordinate 48 | */ 49 | fromSpherical(radius, angle_in_degrees, azimuth_in_degrees) { 50 | const angle_in_radians = angle_in_degrees * Math.PI / 180.0; 51 | const azimuth_in_radians = azimuth_in_degrees * Math.PI / 180.0; 52 | 53 | return { 54 | x: +this.center_coordinate.x + +radius * Math.sin(azimuth_in_radians) * Math.cos(angle_in_radians), 55 | y: +this.center_coordinate.y + +radius * Math.sin(azimuth_in_radians) * Math.sin(angle_in_radians), 56 | z: +this.center_coordinate.z + +radius * Math.cos(azimuth_in_radians), 57 | }; 58 | } 59 | 60 | /** 61 | * @param {Object} coordinate 62 | * @param {Int} coordinate.x 63 | * @param {Int} coordinate.y 64 | * @return {Object} coordinate 65 | * @return {Float} coordinate.dx 66 | * @return {Float} coordinate.dy 67 | * @return {Float} coordinate.radius 68 | * @return {Float} coordinate.radians 69 | * @return {Float} coordinate.degrees 70 | */ 71 | getVectorToPoint(coordinate) { 72 | const dx = coordinate.x - +this.center_coordinate.x; 73 | const dy = coordinate.y - +this.center_coordinate.y; 74 | const hypotenuse = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2) ); 75 | const radians = adjustRadiansForQuadrant(Math.atan(dy / dx)); 76 | const degrees = this.radiansToDegrees(radians); 77 | 78 | function adjustRadiansForQuadrant(radians) { 79 | // In Quadrant 1 80 | if(0 <= dx && 0 <= dy) { 81 | return radians; 82 | } 83 | // In Quadrant 2 84 | if(dx <= 0 && 0 < dy) { 85 | return radians + (6 / 6) * Math.PI; 86 | } 87 | // In Quadrant 3 88 | if(dx < 0 && dy <= 0) { 89 | return radians + (6 / 6) * Math.PI; 90 | } 91 | // In Quadrant 4 92 | if( 0 <= dx && dy < 0) { 93 | return (12 / 6) * Math.PI + radians; 94 | } 95 | } 96 | 97 | return { 98 | dx: dx, 99 | dy: dy, 100 | radius: hypotenuse, 101 | radians: radians, 102 | degrees: degrees, 103 | }; 104 | } 105 | 106 | radiansToDegrees(radians) { 107 | return (180 / Math.PI) * radians; 108 | } 109 | } -------------------------------------------------------------------------------- /src/DotMatrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class DotMatrix 3 | * @param {Node} rootSvg - The svg to append the dots too => root element of the matrix 4 | * @param {Number} spacing - The spacing between coordinates of dots 5 | * @param {Number} padding - The padding on the outside of the svg container 6 | * @param {Number} colPaddingAdjust - The padding adjustment used to calculate columns with given a specific width 7 | * @param {Number} rowPaddingAdjust - The padding adjustment used to calculate row with given a specific height 8 | * @param {String} svgBackgroundColor - The background color of the whole svg 9 | * @param {Number} distanceToFear - The threshold of when the dot will fear and run away from the mouse 10 | * @param {Number} distanceToStep - The size of the initial step away from the mouse once in the fear zone 11 | * @param {Number} animationDelay - The animation delay before returning back home -> in milleseconds 12 | * @param {Number} dotRadius - Radius of the dots 13 | * @param {String} dotFillColor - The fill color of the dot -> default is random 14 | * @param {String} cssClassGoingHome - The class to animate the dot going home -> also used in the style tag injection 15 | * @property {Object} duration - Object to contain fromHome and backHome animation duration properties 16 | * @property {String} fromHome - The animation duration fromHome or away from the mouse 17 | * @property {String} backHome - The animation duration backHome or when the dots returns to it's original position 18 | * @property {Object} timing - Object to contain fromHome and backHome animation timing properties 19 | * @property {String} fromHome - The animation timing fromHome or away from the mouse 20 | * @property {String} backHome - The animation timing backHome or when the dots returns to it's original position 21 | * @param {Number} width - The specified width of the svg container 22 | * @param {Number} height - The specified height of the svg container 23 | * @param {Number} rows - The specified rows of the svg container 24 | * @param {Number} columns - The specified columns of the svg container 25 | */ 26 | class DotMatrix { 27 | constructor(rootSvg, args) { 28 | // Short circuit defaults 29 | this.args = args; 30 | this.rootSvg = rootSvg; 31 | this.colPaddingAdjust = 0; 32 | this.animationDelay = args.animationDelay == false ? false : args.animationDelay || 500; 33 | this.distanceToFear = args.distanceToFear || 50; 34 | this.distanceToStep = args.distanceToStep || 10; 35 | this.dotFillColor = args.dotFillColor || 'black'; 36 | this.dotRadius = args.dotRadius || 5; 37 | this.dotType = args.dotType || 'smart'; 38 | this.letterFillColor = args.letterFillColor || 'white'; 39 | this.padding = args.padding || 30; 40 | this.rowPaddingAdjust = 0; 41 | this.spacing = args.spacing || 30; 42 | this.svgBackgroundColor = args.svgBackgroundColor || 'black'; 43 | this.wordsList = args.wordsList; 44 | this.cssClassGoingHome = args.cssClassGoingHome || 'animate_going_home'; 45 | this.timing = args.timing || {}; 46 | this.duration = args.duration || {}; 47 | this.duration.fromHome = Object(args.duration).hasOwnProperty('fromHome') ? args.duration.fromHome : '0.1s'; 48 | this.duration.backHome = Object(args.duration).hasOwnProperty('backHome') ? args.duration.backHome : '1s'; 49 | this.timing.fromHome = Object(args.timing).hasOwnProperty('fromHome') ? args.timing.fromHome : 'ease'; 50 | this.timing.backHome = Object(args.timing).hasOwnProperty('backHome') ? args.timing.backHome : 'ease'; 51 | this.dotColorPattern = args.dotColorPattern || 'random'; 52 | this.patternColors = args.patternColors || [ 53 | 'red', 54 | 'orange', 55 | 'yellow', 56 | 'green', 57 | 'cyan', 58 | 'skyblue', 59 | 'blue', 60 | 'indigo', 61 | 'violet', 62 | 'grey', 63 | ]; 64 | 65 | // Is viewport less than or equal to 991 66 | this.isDesktop = window.innerWidth >= 992; 67 | 68 | // Specified dimensions 69 | this.width = args.width; 70 | this.height = args.height; 71 | 72 | // If width and height are set in the config calculate the cols and rows 73 | if (this.width && this.height) this.calculated = this.calculateColumnsAndRows(); 74 | 75 | // Ternaries checking for the property "calculated" meaning the width and height were explicitly set 76 | this.rows = this.calculated ? 77 | this.calculated.rows : 78 | args.rows || 40; 79 | 80 | this.columns = this.calculated ? 81 | this.calculated.columns : 82 | args.columns || 40; 83 | 84 | // If width and height are explicitly set we'll set the dimensions to those else we'll calculate the dimensions of the specified cols and rows 85 | this.calculated ? 86 | this.setDimensions(this.width, this.height) 87 | : this.calculateDimensions(); 88 | this.injectCss(); 89 | this.createMatrix(); 90 | 91 | return this; 92 | } 93 | 94 | calculateColumnsAndRows() { 95 | const widthMinusPadding = this.width - (this.padding * 2); 96 | const heightMinusPadding = this.height - (this.padding * 2); 97 | const columns = this.evaluateSpace(widthMinusPadding); 98 | const rows = this.evaluateSpace(heightMinusPadding); 99 | this.colPaddingAdjust = this.paddingAdjustment(widthMinusPadding); 100 | this.rowPaddingAdjust = this.paddingAdjustment(heightMinusPadding); 101 | 102 | return { 103 | columns, 104 | rows, 105 | }; 106 | } 107 | 108 | evaluateSpace(lengthMinusPadding) { 109 | const quotient = lengthMinusPadding / this.spacing; 110 | 111 | // Checking if there is enough space to render an extra row or column 112 | // Checking that val is a whole number and not a float 113 | if (Number.isInteger(quotient)) { 114 | return quotient + 1; 115 | } 116 | 117 | return quotient; 118 | } 119 | 120 | paddingAdjustment(lengthMinusPadding) { 121 | 122 | const quotient = lengthMinusPadding / this.spacing; 123 | const quotientInt = parseInt(quotient); 124 | const decimal = ((quotient - quotientInt) / 2) * this.spacing 125 | return decimal; 126 | } 127 | 128 | calculateDimensions() { 129 | const columnSpacing = this.spacing * (this.columns - 1); 130 | const rowSpacing = this.spacing * (this.rows - 1); 131 | const paddingAggregate = this.padding * 2; 132 | 133 | const width = columnSpacing + paddingAggregate; 134 | const height = rowSpacing + paddingAggregate; 135 | 136 | // Set svg height and width dynamically 137 | this.rootSvg.setAttribute('width', width); 138 | this.rootSvg.setAttribute('height', height); 139 | } 140 | 141 | setDimensions(width, height) { 142 | if (!width || !height) return false; 143 | this.rootSvg.setAttribute('width', width) 144 | this.rootSvg.setAttribute('height', height); 145 | } 146 | 147 | createMatrix() { 148 | for (let i = 0; i < this.rows; i++) { 149 | for (let j = 0; j < this.columns; j++) { 150 | let coordinate = { 151 | x: this.padding + this.colPaddingAdjust + this.spacing * j, 152 | y: this.padding + this.rowPaddingAdjust + this.spacing * i 153 | } 154 | 155 | // Decide type of dot 156 | switch(this.dotType) { 157 | case 'smart': 158 | new SmartDot(this.rootSvg, { 159 | homeCoordinate: coordinate, 160 | uniqueIdentifier: i, 161 | distanceToFear: this.distanceToFear, 162 | distanceToStep: this.distanceToStep, 163 | animationDelay: this.animationDelay, 164 | dotRadius: this.dotRadius, 165 | dotFillColor: this.getColor(i, j), 166 | cssClassGoingHome: this.cssClassGoingHome, 167 | isDesktop: this.isDesktop, 168 | }); 169 | break; 170 | case 'letter': 171 | const word = this.wordsList ? this.wordsList[i] : 'Z'; 172 | 173 | new LetterDot(this.rootSvg, { 174 | homeCoordinate: coordinate, 175 | uniqueIdentifier: i, 176 | distanceToFear: this.distanceToFear, 177 | animationDelay: this.animationDelay, 178 | dotRadius: this.dotRadius, 179 | dotFillColor: this.getColor(i, j), 180 | dotLetter: word ? word.charAt(j) : false, 181 | letterFillColor: this.args.letterFillColor || 'white', 182 | isDesktop: this.isDesktop, 183 | }); 184 | break; 185 | } 186 | } 187 | } 188 | } 189 | 190 | getColor(row, column) { 191 | let color; 192 | let startIndex; 193 | let offsetIndex; 194 | switch(this.dotColorPattern) { 195 | case 'fill': 196 | color = this.dotFillColor; 197 | break; 198 | case 'random': 199 | color = '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6); 200 | break; 201 | case 'diagonal': 202 | // Diagonal Rainbow 203 | startIndex = (column % this.patternColors.length) - (row % this.patternColors.length); 204 | offsetIndex = startIndex + 1; 205 | // Pass undefined to wrap array and combat weird slice behavior when startIndex is -1 206 | color = this.patternColors.slice(startIndex, startIndex == -1 ? undefined : offsetIndex); 207 | break; 208 | case 'horizontal': 209 | // Vertical Rainbow 210 | startIndex = row % this.patternColors.length; 211 | offsetIndex = -1; 212 | 213 | if ( startIndex < this.patternColors.length ) { 214 | offsetIndex = startIndex + 1; 215 | } 216 | 217 | color = this.patternColors.slice(startIndex,offsetIndex); 218 | break; 219 | case 'vertical': 220 | // Horizontal Rainbow 221 | startIndex = column % this.patternColors.length; 222 | offsetIndex = -1; 223 | 224 | if ( startIndex < this.patternColors.length ) { 225 | offsetIndex = startIndex + 1; 226 | } 227 | 228 | color = this.patternColors.slice(startIndex,offsetIndex); 229 | } 230 | 231 | return color; 232 | } 233 | 234 | injectCss() { 235 | const existingStyleTag = document.querySelector('.dot-matrix-style'); 236 | const letterDotStyleDeclaration = ` 237 | svg { 238 | background-color: ${this.svgBackgroundColor}; 239 | display: block; 240 | } 241 | 242 | text { 243 | transition: ease opacity .5s; 244 | } 245 | 246 | circle { 247 | transition: ease opacity 1s; 248 | } 249 | `; 250 | 251 | const smartDotStyleDeclaration = ` 252 | svg { 253 | background-color: ${this.svgBackgroundColor}; 254 | display: block; 255 | } 256 | 257 | circle, 258 | text { 259 | -webkit-transition-timing-function: ${this.timing.fromHome || 'ease'}; 260 | -o-transition-timing-function: ${this.timing.fromHome || 'ease'}; 261 | transition-timing-function: ${this.timing.fromHome || 'ease'}; 262 | -webkit-transition-property: -webkit-transform; 263 | transition-property: -webkit-transform; 264 | -o-transition-property: transform; 265 | transition-property: transform; 266 | transition-property: transform, -webkit-transform; 267 | -webkit-transition-duration: ${this.duration.fromHome || '0.1s'}; 268 | -o-transition-duration: ${this.duration.fromHome || '0.1s'}; 269 | transition-duration: ${this.duration.fromHome || '0.1s'}; 270 | } 271 | 272 | 273 | circle.${this.cssClassGoingHome || 'animate_going_home'}, 274 | text.${this.cssClassGoingHome || 'animate_going_home'} { 275 | -webkit-transition-timing-function: ${this.timing.backHome || 'ease'}; 276 | -o-transition-timing-function: ${this.timing.backHome || 'ease'}; 277 | transition-timing-function: ${this.timing.backHome || 'ease'}; 278 | -webkit-transition-property: -webkit-transform; 279 | transition-property: -webkit-transform; 280 | -o-transition-property: transform; 281 | transition-property: transform; 282 | transition-property: transform, -webkit-transform; 283 | -webkit-transition-duration: ${this.duration.backHome || '1s'}; 284 | -o-transition-duration: ${this.duration.backHome || '1s'}; 285 | transition-duration: ${this.duration.backHome || '1s'}; 286 | } 287 | `; 288 | 289 | const styleDeclaration = this.dotType === 'smart' ? 290 | smartDotStyleDeclaration : 291 | letterDotStyleDeclaration; 292 | 293 | if (existingStyleTag) { 294 | existingStyleTag.textContent = styleDeclaration; 295 | return false; 296 | } 297 | 298 | const style = document.createElement('style'); 299 | style.classList = 'dot-matrix-style'; 300 | style.textContent = styleDeclaration; 301 | document.querySelector('head').appendChild(style); 302 | } 303 | } -------------------------------------------------------------------------------- /src/LetterDot.js: -------------------------------------------------------------------------------- 1 | class LetterDot extends BaseDot { 2 | constructor(svg, args) { 3 | super(svg, args); 4 | this.dotLetter = args.dotLetter; 5 | this.letterFillColor = args.letterFillColor || 'white'; 6 | 7 | if (this.dotLetter) this.insertTextElement(); 8 | } 9 | 10 | insertTextElement() { 11 | const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); 12 | this.fontSize = parseFloat(this.dotRadius / .3); 13 | const xOffset = parseFloat(this.coordinates.home.x); 14 | const yOffset = parseFloat(this.coordinates.home.y) + this.dotRadius; 15 | 16 | /** 17 | * RECTS ARE POSSIBLE - TODO: ALLOW RECTANGLES 18 | * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect 19 | */ 20 | text.classList.add('ease-in'); 21 | text.setAttribute('x', xOffset); 22 | text.setAttribute('y', yOffset); 23 | text.setAttribute('text-anchor', 'middle'); 24 | text.textContent = this.dotLetter; 25 | text.setAttribute('style', `fill: ${this.letterFillColor}; font-size:${this.fontSize}px; opacity: 0;`); 26 | this.text = text; 27 | this.svg.appendChild(this.text); 28 | } 29 | 30 | delegate_touchmove(event) { 31 | // Save our poor CPU 32 | event.stopPropagation(); 33 | event.preventDefault(); 34 | 35 | const touch = event.touches[0]; 36 | this.respondToInput({ 37 | x: touch.clientX, 38 | y: touch.clientY 39 | }, { 40 | baseState: this.baseState.bind(this), 41 | alteredState: () => { 42 | this.hideDot(); 43 | this.showText(); 44 | }, 45 | }); 46 | } 47 | 48 | delegate_mousemove(event) { 49 | // Save our poor CPU 50 | event.stopPropagation(); 51 | event.preventDefault(); 52 | 53 | this.respondToInput({ 54 | x: event.layerX, 55 | y: event.layerY 56 | }, { 57 | baseState: this.baseState.bind(this), 58 | alteredState: this.alteredState.bind(this), 59 | }); 60 | } 61 | 62 | baseState() { 63 | // If no text element bail 64 | if (!this.text) return false; 65 | 66 | if (this.animationDelay === false) { 67 | this.showDot(); 68 | this.hideText(); 69 | 70 | return false; 71 | } 72 | 73 | if (this[`timeout_return_home_${this.uniqueIdentifier}`]) clearTimeout(this[`timeout_return_home_${this.uniqueIdentifier}`]); 74 | this[`timeout_return_home_${this.uniqueIdentifier}`] = setTimeout((() => { 75 | this.showDot(); 76 | this.hideText(); 77 | }).bind(this), this.animationDelay); 78 | } 79 | 80 | alteredState() { 81 | if (!this.text) return false; 82 | this.hideDot(); 83 | this.showText(); 84 | } 85 | 86 | showDot() { 87 | this.dot.setAttribute('style', 'opacity: 1'); 88 | } 89 | 90 | hideDot() { 91 | this.dot.setAttribute('style', 'opacity: 0'); 92 | } 93 | 94 | showText() { 95 | this.text.setAttribute('style', `fill: ${this.letterFillColor}; font-size:${this.fontSize}px; opacity: 1;`) 96 | } 97 | 98 | hideText() { 99 | this.text.setAttribute('style', `fill: ${this.letterFillColor}; font-size:${this.fontSize}px; opacity: 0;`) 100 | } 101 | } -------------------------------------------------------------------------------- /src/SmartDot.js: -------------------------------------------------------------------------------- 1 | class SmartDot extends BaseDot { 2 | constructor(svg, args) { 3 | super(svg, args); 4 | this.distanceToStep = args.distanceToStep || 50; 5 | this.cssClassGoingHome = args.cssClassGoingHome || 'animate_going_home'; 6 | } 7 | 8 | baseState() { 9 | if (this.animationDelay === false) { 10 | // Add going home animate 11 | this.dot.classList.add(this.cssClassGoingHome); 12 | 13 | this.setWhereIShouldBe( 14 | { 15 | dx: 0, 16 | dy: 0 17 | }, 18 | this.coordinates.home 19 | ); 20 | 21 | return false; 22 | } 23 | 24 | if (this[`timeout_return_home_${this.uniqueIdentifier}`]) clearTimeout(this[`timeout_return_home_${this.uniqueIdentifier}`]); 25 | this[`timeout_return_home_${this.uniqueIdentifier}`] = setTimeout((() => { 26 | this.dot.classList.add(this.cssClassGoingHome); 27 | this.setWhereIShouldBe({ dx: 0, dy: 0 }, this.coordinates.home); 28 | }).bind(this), this.animationDelay); 29 | } 30 | 31 | alteredState() { 32 | // Toggle animation class 33 | this.dot.classList.remove(this.cssClassGoingHome); 34 | 35 | // Get Where I Should Go - Absolute Position 36 | const move_to_coords_absolute = (new CoordinateTranslator) 37 | .setCenterCoordinate(this.coordinates.current) 38 | .fromPolar(this.distanceToStep, this.vector_from_mouse_to_me.degrees); 39 | 40 | // Get Deltas between current coordinate and home - Relative Position 41 | const move_to_coords_relative = (new CoordinateTranslator) 42 | .setCenterCoordinate(this.coordinates.home) 43 | .getVectorToPoint(move_to_coords_absolute); 44 | 45 | // Make the change happen on the DOM 46 | this.setWhereIShouldBe(move_to_coords_relative, move_to_coords_absolute); 47 | } 48 | 49 | delegate_touchmove(event) { 50 | // Save our poor CPU 51 | event.stopPropagation(); 52 | event.preventDefault(); 53 | const touch = event.touches[0]; 54 | this.respondToInput({ 55 | x: touch.clientX, 56 | y: touch.clientY 57 | }, { 58 | baseState: this.baseState.bind(this), 59 | alteredState: this.alteredState.bind(this), 60 | }); 61 | } 62 | 63 | delegate_mousemove(event) { 64 | // Save our poor CPU 65 | event.stopPropagation(); 66 | event.preventDefault(); 67 | this.respondToInput({ 68 | x: event.layerX, 69 | y: event.layerY 70 | }, { 71 | baseState: this.baseState.bind(this), 72 | alteredState: this.alteredState.bind(this), 73 | }); 74 | } 75 | 76 | isMouseNearMe(vector_from_mouse_to_me) { 77 | if (vector_from_mouse_to_me.radius < this.distanceToFear) { 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | setWhereIShouldBe(delta_coords, absolute_coords) { 84 | // Absolute Position 85 | this.coordinates.current.x = absolute_coords.x; 86 | this.coordinates.current.y = absolute_coords.y; 87 | 88 | // Relative Position 89 | this.dot.setAttribute('style', `transform: translate(${delta_coords.dx}px, ${delta_coords.dy}px)`); 90 | } 91 | } --------------------------------------------------------------------------------