├── image ├── gif.gif └── sort_project_pic.png ├── README.md ├── style.css ├── index.html ├── timer_algos.js ├── algorithms.js ├── sketch.js └── libraries └── p5.dom.js /image/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgoel1220/sorting-visualizer/HEAD/image/gif.gif -------------------------------------------------------------------------------- /image/sort_project_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sgoel1220/sorting-visualizer/HEAD/image/sort_project_pic.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sorting Visualizer 2 | 3 | ![Project Image](https://github.com/shubh67678/sorting-visualizer/blob/master/image/gif.gif) 4 | > [Live demo](https://sgoel1220.github.io/sorting-visualizer/) 5 | 6 | --- 7 | 8 | ## Description 9 | 10 | This project helps one to visualize a sorting algorithm. Each element of the array is displayed as a bar. The operations are colour coded such that: 11 | 12 | 1. Red - Swap 13 | 2. Blue - Comparison 14 | 3. Green - Element is in sorted position 15 | 16 | It compares the time taken by the different algorithm for sorting the array. 17 | 18 | 19 | 20 | ### Technologies 21 | 22 | - HTML 23 | - CSS 24 | - Javascript (p5.js) 25 | 26 | --- 27 | 28 | 29 | ## References 30 | 31 | Inspiration taken from [coding train](https://www.youtube.com/watch?v=67k3I2GxTH8). 32 | 33 | Useful links discribing the algorithms used 34 | 35 | - [Bubble Sort](https://en.wikipedia.org/wiki/Bubble_sort) 36 | - [Selection Sort](https://en.wikipedia.org/wiki/Selection_sort) 37 | - [Merge Sort](https://en.wikipedia.org/wiki/Merge_sort) 38 | - [Quick Sort](https://en.wikipedia.org/wiki/Quicksort) 39 | 40 | 41 | [Back To The Top](#read-me-template) 42 | 43 | --- 44 | 45 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Poppins:400,500,600,700&display=swap"); 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: "Poppins", sans-serif; 7 | color: white; 8 | } 9 | body { 10 | background: #f2f2f2; 11 | } 12 | nav { 13 | background: #1b1b1b; 14 | width: 100%; 15 | } 16 | nav:after { 17 | content: ""; 18 | clear: both; 19 | display: table; 20 | } 21 | nav ul { 22 | list-style: none; 23 | position: relative; 24 | align-items: center; 25 | } 26 | nav ul li { 27 | display: inline-block; 28 | margin: 0 5px; 29 | color: #1b1b1b; 30 | } 31 | nav ul li a { 32 | color: white; 33 | text-decoration: none; 34 | line-height: 70px; 35 | font-size: 16px; 36 | padding: 8px 15px; 37 | text-align: center; 38 | } 39 | 40 | /* nav ul li a:hover { 41 | border-radius: 5px; 42 | border-bottom: 1px solid #33ffff; 43 | } */ 44 | .clickable:hover { 45 | border-radius: 5px; 46 | border-bottom: 1px solid #33ffff; 47 | cursor: pointer; 48 | } 49 | nav ul ul { 50 | position: absolute; 51 | top: 70px; 52 | opacity: 0; 53 | visibility: hidden; 54 | } 55 | nav ul :hover > ul { 56 | opacity: 1; 57 | visibility: visible; 58 | } 59 | 60 | nav ul ul li { 61 | background-color: #1b1b1b; 62 | position: relative; 63 | margin: 0px; 64 | width: 200px; 65 | float: none; 66 | display: list-item; 67 | border-bottom: 1px solid; 68 | } 69 | 70 | .range-input { 71 | background: teal; 72 | -webkit-appearance: none; 73 | height: 2px; 74 | outline: none; 75 | border-radius: 2px; 76 | } 77 | 78 | .range-input::-webkit-slider-thumb { 79 | -webkit-appearance: none; 80 | width: 15px; 81 | height: 15px; 82 | border-radius: 50%; 83 | background: #33ffff; 84 | cursor: pointer; 85 | } 86 | .range-input::-moz-range-thumb { 87 | -webkit-appearance: none; 88 | width: 15px; 89 | height: 15px; 90 | border-radius: 50%; 91 | background: #33ffff; 92 | cursor: pointer; 93 | } 94 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | sort_visual 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /timer_algos.js: -------------------------------------------------------------------------------- 1 | function bubbleSort_t(arr) { 2 | var swapped = false; 3 | 4 | for (let i = 0; i < arr.length; i++) { 5 | for (let j = 0; j < arr.length - i - 1; j++) { 6 | //comapre return arr[j]>arr[j+1] 7 | 8 | if (compare(arr, j, j + 1)) { 9 | swap(arr, j, j + 1); 10 | 11 | swapped = true; 12 | } 13 | } 14 | } 15 | if (swapped == false) { 16 | return; 17 | } 18 | } 19 | 20 | function selectionSort_t(arr) { 21 | for (let i = 0; i < arr.length - 1; i++) { 22 | let minIdx = i; 23 | for (let j = i + 1; j < arr.length; j++) { 24 | if (compare(arr, minIdx, j)) { 25 | minIdx = j; 26 | } 27 | } 28 | 29 | swap(arr, i, minIdx); 30 | } 31 | } 32 | function quickSortLomuto_t(arr) { 33 | return _quickSortLomuto(arr, 0, arr.length - 1); 34 | } 35 | 36 | function _quickSortLomuto(arr, left, right) { 37 | if (left >= right) { 38 | return; 39 | } 40 | let partitionGenerator = _partitionLomuto(arr, left, right); 41 | 42 | let result = partitionGenerator.next(); 43 | 44 | let idx = result.value; 45 | return _quickSortLomuto(arr, left, idx - 1); 46 | return _quickSortLomuto(arr, idx + 1, right); 47 | } 48 | 49 | function _partitionLomuto(arr, left, right) { 50 | let pivot = arr[right].val; 51 | let i = left - 1; 52 | for (let j = left; j < right; j++) { 53 | arr[right].pivot = true; 54 | if (arr[j].val < pivot) { 55 | compare(arr, j, right); 56 | swap(arr, ++i, j); 57 | } 58 | } 59 | swap(arr, i + 1, right); 60 | return i + 1; 61 | } 62 | 63 | // Merge Sort Implentation (Recursion) 64 | function mergeSort_t(unsortedArray) { 65 | // No need to sort the array if the array only has one element or empty 66 | if (unsortedArray.length <= 1) { 67 | return unsortedArray; 68 | } 69 | // In order to divide the array in half, we need to figure out the middle 70 | const middle = Math.floor(unsortedArray.length / 2); 71 | 72 | // This is where we will be dividing the array into left and right 73 | const left = unsortedArray.slice(0, middle); 74 | const right = unsortedArray.slice(middle); 75 | 76 | // Using recursion to combine the left and right 77 | return merge(mergeSort(left), mergeSort(right)); 78 | } 79 | function merge(left, right) { 80 | let resultArray = [], 81 | leftIndex = 0, 82 | rightIndex = 0; 83 | 84 | // We will concatenate values into the resultArray in order 85 | while (leftIndex < left.length && rightIndex < right.length) { 86 | if (left[leftIndex].val < right[rightIndex].val) { 87 | resultArray.push(left[leftIndex]); 88 | leftIndex++; // move left array cursor 89 | } else { 90 | resultArray.push(right[rightIndex]); 91 | rightIndex++; // move right array cursor 92 | } 93 | } 94 | 95 | // We need to concat here because there will be one element remaining 96 | // from either left OR the right 97 | // return resultArray.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); 98 | if (leftIndex < left.length) { 99 | resultArray.concat(left.slice(leftIndex)); 100 | } else { 101 | if (rightIndex < right.length) { 102 | resultArray.concat(right.slice(rightIndex)); 103 | } 104 | } 105 | return resultArray; 106 | } 107 | -------------------------------------------------------------------------------- /algorithms.js: -------------------------------------------------------------------------------- 1 | function* bubbleSort(arr) { 2 | var swapped = false; 3 | console.log("in"); 4 | 5 | for (let i = 0; i < arr.length; i++) { 6 | for (let j = 0; j < arr.length - i - 1; j++) { 7 | //comapre return arr[j]>arr[j+1] 8 | 9 | if (compare(arr, j, j + 1)) { 10 | swap(arr, j, j + 1); 11 | 12 | swapped = true; 13 | } 14 | yield; 15 | } 16 | } 17 | if (swapped == false) { 18 | return; 19 | } 20 | } 21 | 22 | function* selectionSort(arr) { 23 | for (let i = 0; i < arr.length - 1; i++) { 24 | let minIdx = i; 25 | for (let j = i + 1; j < arr.length; j++) { 26 | if (compare(arr, minIdx, j)) { 27 | minIdx = j; 28 | } 29 | yield; 30 | } 31 | 32 | swap(arr, i, minIdx); 33 | yield; 34 | } 35 | } 36 | function* mergeSort(arr) { 37 | yield* _mergeSort(arr, 0, arr.length - 1); 38 | } 39 | 40 | function* _mergeSort(arr, l, r) { 41 | if (l >= r) return; 42 | const m = l + Math.floor((r - l) / 2); 43 | yield* _mergeSort(arr, l, m); 44 | yield* _mergeSort(arr, m + 1, r); 45 | yield* _merge(arr, l, m, r); 46 | } 47 | 48 | function* _merge(arr, l, m, r) { 49 | const n1 = m - l + 1; 50 | const n2 = r - m; 51 | let L = new Array(n1); 52 | let R = new Array(n2); 53 | for (let i = 0; i < n1; i++) L[i] = arr[l + i]; 54 | for (let j = 0; j < n2; j++) R[j] = arr[m + 1 + j]; 55 | 56 | let i = 0; 57 | let j = 0; 58 | let k = l; 59 | 60 | while (i < n1 && j < n2) { 61 | find_and_compare(L[i].val, R[j].val); 62 | if (L[i].val < R[j].val) { 63 | find_and_swap(arr[k].val, L[i].val); 64 | arr[k] = L[i]; 65 | k++; 66 | i++; 67 | } 68 | //increment after the value given 69 | else { 70 | find_and_swap(arr[k].val, R[j].val); 71 | 72 | arr[k] = R[j]; 73 | k++; 74 | j++; 75 | } 76 | yield; 77 | } 78 | 79 | while (i < n1) { 80 | find_and_swap(arr[k].val, L[i].val); 81 | 82 | arr[k++] = L[i++]; 83 | yield; 84 | } 85 | 86 | while (j < n2) { 87 | find_and_swap(arr[k].val, R[j].val); 88 | 89 | arr[k++] = R[j++]; 90 | yield; 91 | } 92 | } 93 | 94 | function* quickSortLomuto(arr) { 95 | //tricky to understand 96 | yield* _quickSortLomuto(arr, 0, arr.length - 1); 97 | } 98 | 99 | function* _quickSortLomuto(arr, left, right) { 100 | if (left >= right) { 101 | return; 102 | } 103 | let partitionGenerator = _partitionLomuto(arr, left, right); 104 | 105 | let result = partitionGenerator.next(); 106 | //we need the pivot so wee run this till we the poviot 107 | while (!result.done) { 108 | result = partitionGenerator.next(); 109 | yield 1; 110 | } 111 | 112 | let idx = result.value; 113 | yield* _quickSortLomuto(arr, left, idx - 1); 114 | yield* _quickSortLomuto(arr, idx + 1, right); 115 | } 116 | 117 | function* _partitionLomuto(arr, left, right) { 118 | let pivot = arr[right].val; 119 | let i = left - 1; 120 | for (let j = left; j < right; j++) { 121 | arr[right].pivot = true; 122 | if (arr[j].val < pivot) { 123 | compare(arr, j, right); 124 | swap(arr, ++i, j); 125 | yield; 126 | } 127 | } 128 | swap(arr, i + 1, right); 129 | yield; 130 | return i + 1; 131 | } 132 | 133 | function swap(arr, x, y) { 134 | //set color and swap 135 | arr[x].swap = true; 136 | arr[y].swap = true; 137 | 138 | temp = arr[x]; 139 | arr[x] = arr[y]; 140 | arr[y] = temp; 141 | } 142 | function compare(arr, x, y) { 143 | //set color and compare 144 | arr[x].compare = true; 145 | arr[y].compare = true; 146 | return arr[x].val > arr[y].val; 147 | } 148 | function find_and_compare(val1, val2) { 149 | //find the loc of val in arr and set the color 150 | index_1 = 0; 151 | index_2 = 0; 152 | for (var i = 0; i < arr.length; i++) { 153 | if (arr[i].val == val1) { 154 | index_1 = i; 155 | } 156 | if (arr[i].val == val2) { 157 | index_2 = i; 158 | } 159 | } 160 | arr[index_1].compare = true; 161 | arr[index_2].compare = true; 162 | } 163 | function find_and_swap(val1, val2) { 164 | //find the loc of val in arr and set the color 165 | index_1 = 0; 166 | index_2 = 0; 167 | for (var i = 0; i < arr.length; i++) { 168 | if (arr[i].val == val1) { 169 | index_1 = i; 170 | } 171 | if (arr[i].val == val2) { 172 | index_2 = i; 173 | } 174 | } 175 | arr[index_1].swap = true; 176 | arr[index_2].swap = true; 177 | } 178 | -------------------------------------------------------------------------------- /sketch.js: -------------------------------------------------------------------------------- 1 | //constants/globals 2 | var arr = Array(0); 3 | const width = window.innerWidth; 4 | const height = window.innerHeight - 200; 5 | var thick = 20; 6 | var len_arr = Math.floor(width / thick); 7 | var sorted_arr = Array(0); 8 | var start_sorting = false; 9 | var frame_rate_val = 40; 10 | var timer_arr = []; 11 | var time_taken = 0; 12 | var pause = false; 13 | //each bar has following properties 14 | class Element { 15 | constructor(val) { 16 | this.val = val; 17 | this.compare = false; 18 | this.swap = false; 19 | this.sub_arr = false; 20 | } 21 | 22 | draw(i, color = 255) { 23 | fill(color); 24 | if (this.compare == true) { 25 | fill(0, 0, 255); 26 | } 27 | if (this.swap == true) { 28 | fill(255, 0, 0); 29 | } 30 | stroke(0); 31 | this.swap = false; 32 | this.compare = false; //reset the vals as i only want one time display 33 | this.pivot = false; //waste delete it later(arr.pivot prop) 34 | rect(i * thick, height - this.val + 1, thick, this.val); 35 | if (thick > 5) { 36 | noStroke(); //mix with rect 37 | ellipse( 38 | i * thick + thick / 2 + 0.5, 39 | height - this.val + 1, 40 | thick - 1 41 | ); 42 | //not draw when rect thin 43 | } 44 | } 45 | } 46 | //function name holder 47 | const algo_dict = { 48 | bubbleSort: bubbleSort, 49 | selectionSort: selectionSort, 50 | mergeSort: mergeSort, 51 | quickSort: quickSortLomuto, 52 | }; 53 | const timer_algo = { 54 | bubbleSort: bubbleSort_t, 55 | selectionSort: selectionSort_t, 56 | mergeSort: mergeSort_t, 57 | quickSort: quickSortLomuto_t, 58 | }; 59 | 60 | 61 | function setup() { 62 | print_hello(); 63 | createCanvas(width, height); 64 | var btns = document.querySelectorAll(".clickable"); //all buttons 65 | // console.log(btns); 66 | for (btn of btns) { 67 | btn.addEventListener("click", function () { 68 | // console.log("clicked", this.id); debugging button 69 | if (this.id == "reset") { 70 | //gets a new arr 71 | //styles the element to default 72 | arr = []; 73 | sorted_arr = []; 74 | start_sorting = false; 75 | frameRate(frame_rate_val); 76 | setup_arr(); 77 | document.getElementById("time").innerHTML = "Time: 0us"; 78 | document.getElementById("frm").value = "40"; 79 | } else { 80 | if (this.id != "") { 81 | if (start_sorting == false) { 82 | //if no other sorting algo was selected 83 | start_sorting = true; 84 | start_sort(this.id); 85 | time_this_algo(this.id); 86 | frameRate(frame_rate_val); 87 | } else { 88 | //if other algo was running 89 | //reset and run this new algo 90 | arr = []; 91 | sorted_arr = []; 92 | start_sorting = false; 93 | frameRate(frame_rate_val); 94 | setup_arr(); 95 | document.getElementById("time").innerHTML = "Time:0us"; 96 | start_sorting = true; 97 | time_this_algo(this.id); 98 | start_sort(this.id); 99 | } 100 | } 101 | } 102 | 103 | return true; 104 | }); 105 | } 106 | slider_control(); //slide control 107 | setup_arr(); //create the arr 108 | // console.log(frameRate()); 109 | } 110 | function time_this_algo(algo) { 111 | //timer start 112 | var t0 = window.performance.now(); 113 | timer_loop = timer_algo[algo](timer_arr); 114 | var t1 = window.performance.now(); 115 | console.log(t1 - t0); 116 | time = (t1 - t0) * 1000; 117 | time = Math.round(time); 118 | t1 = 0; 119 | t0 = 0; 120 | if (time == 0) { 121 | time = 0.01; 122 | } 123 | if (time < 1000) { 124 | document.getElementById("time").innerHTML = 125 | "Time: " + String(time) + "us"; 126 | } else { 127 | document.getElementById("time").innerHTML = 128 | "Time: " + String(Math.round(time / 1000)) + "ms"; 129 | } 130 | //timer stop 131 | //store the diff 132 | } 133 | function slider_control() { 134 | var size_slider = document.getElementById("data_size"); 135 | size_slider.oninput = function () { 136 | thick = 62 - size_slider.value; 137 | //higher the slider more the bars 138 | len_arr = Math.floor(width / thick); 139 | //reset everything 140 | arr = []; 141 | sorted_arr = []; 142 | start_sorting = false; 143 | frameRate(frame_rate_val); 144 | setup_arr(); 145 | // console.log(arr); 146 | }; 147 | var frm_slider = document.getElementById("frm"); 148 | // console.log(frm_slider); 149 | frm_slider.oninput = function () { 150 | //cant cange vals superfast 151 | //so we do according to range of slider 152 | if (this.value == 0) { 153 | frame_rate_val = 0; 154 | frameRate(0); 155 | } 156 | if (1 < this.value && this.value <= 10) { 157 | if (frameRate() != 5) { 158 | frame_rate_val = 5; 159 | frameRate(5); 160 | } 161 | } 162 | if (11 < this.value && this.value <= 20) { 163 | if (frameRate() != 20) { 164 | frame_rate_val = 20; 165 | frameRate(20); 166 | } 167 | } 168 | if (21 < this.value && this.value <= 30) { 169 | if (frameRate() != 40) { 170 | frame_rate_val = 40; 171 | frameRate(40); 172 | } 173 | } 174 | if (35 < this.value && this.value <= 40) { 175 | if (frameRate() != 60) { 176 | frame_rate_val = 60; 177 | frameRate(60); 178 | } 179 | } 180 | }; 181 | } 182 | function setup_arr() { 183 | for (let i = 0; i < len_arr; i++) { 184 | push_value = random(thick, height - thick); //ellipse height 185 | arr.push(new Element(push_value)); 186 | sorted_arr.push(push_value); 187 | timer_arr.push(new Element(push_value)); 188 | } 189 | sort_the_arr(sorted_arr); 190 | } 191 | function start_sort(algo) { 192 | loop_counter = algo_dict[algo](arr); 193 | } 194 | function draw() { 195 | background(0); 196 | 197 | if (start_sorting == true) { 198 | loop_counter.next(); 199 | } 200 | draw_arr(); 201 | } 202 | 203 | function draw_arr() { 204 | for (let i = 0; i < arr.length; i++) { 205 | arr[i].draw(i); 206 | if (sorted_arr[i] == arr[i].val) { 207 | arr[i].draw(i, color(0, 255, 0)); 208 | } 209 | } 210 | } 211 | function sort_the_arr(arr) { 212 | for (let i = 0; i < arr.length; i++) { 213 | for (let j = 0; j < arr.length - i - 1; j++) { 214 | if (arr[j] > arr[j + 1]) { 215 | temp = arr[j + 1]; 216 | arr[j + 1] = arr[j]; 217 | arr[j] = temp; 218 | } 219 | } 220 | } 221 | } 222 | 223 | function print_hello() { 224 | console.log('Hellooo human!'); 225 | } 226 | -------------------------------------------------------------------------------- /libraries/p5.dom.js: -------------------------------------------------------------------------------- 1 | /*! p5.dom.js v0.3.4 Jan 19, 2017 */ 2 | /** 3 | *

The web is much more than just canvas and p5.dom makes it easy to interact 4 | * with other HTML5 objects, including text, hyperlink, image, input, video, 5 | * audio, and webcam.

6 | *

There is a set of creation methods, DOM manipulation methods, and 7 | * an extended p5.Element that supports a range of HTML elements. See the 8 | * 9 | * beyond the canvas tutorial for a full overview of how this addon works. 10 | * 11 | *

Methods and properties shown in black are part of the p5.js core, items in 12 | * blue are part of the p5.dom library. You will need to include an extra file 13 | * in order to access the blue functions. See the 14 | * using a library 15 | * section for information on how to include this library. p5.dom comes with 16 | * p5 complete or you can download the single file 17 | * 18 | * here.

19 | *

See tutorial: beyond the canvas 20 | * for more info on how to use this libary. 21 | * 22 | * @module p5.dom 23 | * @submodule p5.dom 24 | * @for p5.dom 25 | * @main 26 | */ 27 | 28 | (function(root, factory) { 29 | if (typeof define === 'function' && define.amd) 30 | define('p5.dom', ['p5'], function(p5) { 31 | factory(p5); 32 | }); 33 | else if (typeof exports === 'object') factory(require('../p5')); 34 | else factory(root['p5']); 35 | })(this, function(p5) { 36 | // ============================================================================= 37 | // p5 additions 38 | // ============================================================================= 39 | 40 | /** 41 | * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' 42 | * prefixes to specify an ID or class respectively, and none for a tag) and returns it as 43 | * a p5.Element. If a class or tag name is given with more than 1 element, 44 | * only the first element will be returned. 45 | * The DOM node itself can be accessed with .elt. 46 | * Returns null if none found. You can also specify a container to search within. 47 | * 48 | * @method select 49 | * @param {String} name id, class, or tag name of element to search for 50 | * @param {String} [container] id, p5.Element, or HTML element to search within 51 | * @return {Object|p5.Element|Null} p5.Element containing node found 52 | * @example 53 | *

54 | * function setup() { 55 | * createCanvas(100, 100); 56 | * //translates canvas 50px down 57 | * select('canvas').position(100, 100); 58 | * } 59 | *
60 | *
61 | * // these are all valid calls to select() 62 | * var a = select('#moo'); 63 | * var b = select('#blah', '#myContainer'); 64 | * var c = select('#foo', b); 65 | * var d = document.getElementById('beep'); 66 | * var e = select('p', d); 67 | * [a, b, c, d, e]; // unused 68 | *
69 | * 70 | */ 71 | p5.prototype.select = function(e, p) { 72 | p5._validateParameters('select', arguments); 73 | var res = null; 74 | var container = getContainer(p); 75 | if (e[0] === '.') { 76 | e = e.slice(1); 77 | res = container.getElementsByClassName(e); 78 | if (res.length) { 79 | res = res[0]; 80 | } else { 81 | res = null; 82 | } 83 | } else if (e[0] === '#') { 84 | e = e.slice(1); 85 | res = container.getElementById(e); 86 | } else { 87 | res = container.getElementsByTagName(e); 88 | if (res.length) { 89 | res = res[0]; 90 | } else { 91 | res = null; 92 | } 93 | } 94 | if (res) { 95 | return this._wrapElement(res); 96 | } else { 97 | return null; 98 | } 99 | }; 100 | 101 | /** 102 | * Searches the page for elements with the given class or tag name (using the '.' prefix 103 | * to specify a class and no prefix for a tag) and returns them as p5.Elements 104 | * in an array. 105 | * The DOM node itself can be accessed with .elt. 106 | * Returns an empty array if none found. 107 | * You can also specify a container to search within. 108 | * 109 | * @method selectAll 110 | * @param {String} name class or tag name of elements to search for 111 | * @param {String} [container] id, p5.Element, or HTML element to search within 112 | * @return {Array} Array of p5.Elements containing nodes found 113 | * @example 114 | *
115 | * function setup() { 116 | * createButton('btn'); 117 | * createButton('2nd btn'); 118 | * createButton('3rd btn'); 119 | * var buttons = selectAll('button'); 120 | * 121 | * for (var i = 0; i < buttons.length; i++) { 122 | * buttons[i].size(100, 100); 123 | * } 124 | * } 125 | *
126 | *
127 | * // these are all valid calls to selectAll() 128 | * var a = selectAll('.moo'); 129 | * a = selectAll('div'); 130 | * a = selectAll('button', '#myContainer'); 131 | * 132 | * var d = select('#container'); 133 | * a = selectAll('p', d); 134 | * 135 | * var f = document.getElementById('beep'); 136 | * a = select('.blah', f); 137 | * 138 | * a; // unused 139 | *
140 | * 141 | */ 142 | p5.prototype.selectAll = function(e, p) { 143 | p5._validateParameters('selectAll', arguments); 144 | var arr = []; 145 | var res; 146 | var container = getContainer(p); 147 | if (e[0] === '.') { 148 | e = e.slice(1); 149 | res = container.getElementsByClassName(e); 150 | } else { 151 | res = container.getElementsByTagName(e); 152 | } 153 | if (res) { 154 | for (var j = 0; j < res.length; j++) { 155 | var obj = this._wrapElement(res[j]); 156 | arr.push(obj); 157 | } 158 | } 159 | return arr; 160 | }; 161 | 162 | /** 163 | * Helper function for select and selectAll 164 | */ 165 | function getContainer(p) { 166 | var container = document; 167 | if (typeof p === 'string' && p[0] === '#') { 168 | p = p.slice(1); 169 | container = document.getElementById(p) || document; 170 | } else if (p instanceof p5.Element) { 171 | container = p.elt; 172 | } else if (p instanceof HTMLElement) { 173 | container = p; 174 | } 175 | return container; 176 | } 177 | 178 | /** 179 | * Helper function for getElement and getElements. 180 | */ 181 | p5.prototype._wrapElement = function(elt) { 182 | var children = Array.prototype.slice.call(elt.children); 183 | if (elt.tagName === 'INPUT' && elt.type === 'checkbox') { 184 | var converted = new p5.Element(elt); 185 | converted.checked = function() { 186 | if (arguments.length === 0) { 187 | return this.elt.checked; 188 | } else if (arguments[0]) { 189 | this.elt.checked = true; 190 | } else { 191 | this.elt.checked = false; 192 | } 193 | return this; 194 | }; 195 | return converted; 196 | } else if (elt.tagName === 'VIDEO' || elt.tagName === 'AUDIO') { 197 | return new p5.MediaElement(elt); 198 | } else if (elt.tagName === 'SELECT') { 199 | return this.createSelect(new p5.Element(elt)); 200 | } else if ( 201 | children.length > 0 && 202 | children.every(function(c) { 203 | return c.tagName === 'INPUT' || c.tagName === 'LABEL'; 204 | }) 205 | ) { 206 | return this.createRadio(new p5.Element(elt)); 207 | } else { 208 | return new p5.Element(elt); 209 | } 210 | }; 211 | 212 | /** 213 | * Removes all elements created by p5, except any canvas / graphics 214 | * elements created by createCanvas or createGraphics. 215 | * Event handlers are removed, and element is removed from the DOM. 216 | * @method removeElements 217 | * @example 218 | *
219 | * function setup() { 220 | * createCanvas(100, 100); 221 | * createDiv('this is some text'); 222 | * createP('this is a paragraph'); 223 | * } 224 | * function mousePressed() { 225 | * removeElements(); // this will remove the div and p, not canvas 226 | * } 227 | *
228 | * 229 | */ 230 | p5.prototype.removeElements = function(e) { 231 | p5._validateParameters('removeElements', arguments); 232 | for (var i = 0; i < this._elements.length; i++) { 233 | if (!(this._elements[i].elt instanceof HTMLCanvasElement)) { 234 | this._elements[i].remove(); 235 | } 236 | } 237 | }; 238 | 239 | /** 240 | * Helpers for create methods. 241 | */ 242 | function addElement(elt, pInst, media) { 243 | var node = pInst._userNode ? pInst._userNode : document.body; 244 | node.appendChild(elt); 245 | var c = media ? new p5.MediaElement(elt) : new p5.Element(elt); 246 | pInst._elements.push(c); 247 | return c; 248 | } 249 | 250 | /** 251 | * Creates a <div></div> element in the DOM with given inner HTML. 252 | * Appends to the container node if one is specified, otherwise 253 | * appends to body. 254 | * 255 | * @method createDiv 256 | * @param {String} [html] inner HTML for element created 257 | * @return {Object|p5.Element} pointer to p5.Element holding created node 258 | * @example 259 | *
260 | * createDiv('this is some text'); 261 | *
262 | */ 263 | 264 | /** 265 | * Creates a <p></p> element in the DOM with given inner HTML. Used 266 | * for paragraph length text. 267 | * Appends to the container node if one is specified, otherwise 268 | * appends to body. 269 | * 270 | * @method createP 271 | * @param {String} [html] inner HTML for element created 272 | * @return {Object|p5.Element} pointer to p5.Element holding created node 273 | * @example 274 | *
275 | * createP('this is some text'); 276 | *
277 | */ 278 | 279 | /** 280 | * Creates a <span></span> element in the DOM with given inner HTML. 281 | * Appends to the container node if one is specified, otherwise 282 | * appends to body. 283 | * 284 | * @method createSpan 285 | * @param {String} [html] inner HTML for element created 286 | * @return {Object|p5.Element} pointer to p5.Element holding created node 287 | * @example 288 | *
289 | * createSpan('this is some text'); 290 | *
291 | */ 292 | var tags = ['div', 'p', 'span']; 293 | tags.forEach(function(tag) { 294 | var method = 'create' + tag.charAt(0).toUpperCase() + tag.slice(1); 295 | p5.prototype[method] = function(html) { 296 | var elt = document.createElement(tag); 297 | elt.innerHTML = typeof html === undefined ? '' : html; 298 | return addElement(elt, this); 299 | }; 300 | }); 301 | 302 | /** 303 | * Creates an <img> element in the DOM with given src and 304 | * alternate text. 305 | * Appends to the container node if one is specified, otherwise 306 | * appends to body. 307 | * 308 | * @method createImg 309 | * @param {String} src src path or url for image 310 | * @param {String} [alt] alternate text to be used if image does not load 311 | * @param {Function} [successCallback] callback to be called once image data is loaded 312 | * @return {Object|p5.Element} pointer to p5.Element holding created node 313 | * @example 314 | *
315 | * createImg('http://p5js.org/img/asterisk-01.png'); 316 | *
317 | */ 318 | p5.prototype.createImg = function() { 319 | p5._validateParameters('createImg', arguments); 320 | var elt = document.createElement('img'); 321 | var args = arguments; 322 | var self; 323 | var setAttrs = function() { 324 | self.width = elt.offsetWidth || elt.width; 325 | self.height = elt.offsetHeight || elt.height; 326 | if (args.length > 1 && typeof args[1] === 'function') { 327 | self.fn = args[1]; 328 | self.fn(); 329 | } else if (args.length > 1 && typeof args[2] === 'function') { 330 | self.fn = args[2]; 331 | self.fn(); 332 | } 333 | }; 334 | elt.src = args[0]; 335 | if (args.length > 1 && typeof args[1] === 'string') { 336 | elt.alt = args[1]; 337 | } 338 | elt.onload = function() { 339 | setAttrs(); 340 | }; 341 | self = addElement(elt, this); 342 | return self; 343 | }; 344 | 345 | /** 346 | * Creates an <a></a> element in the DOM for including a hyperlink. 347 | * Appends to the container node if one is specified, otherwise 348 | * appends to body. 349 | * 350 | * @method createA 351 | * @param {String} href url of page to link to 352 | * @param {String} html inner html of link element to display 353 | * @param {String} [target] target where new link should open, 354 | * could be _blank, _self, _parent, _top. 355 | * @return {Object|p5.Element} pointer to p5.Element holding created node 356 | * @example 357 | *
358 | * createA('http://p5js.org/', 'this is a link'); 359 | *
360 | */ 361 | p5.prototype.createA = function(href, html, target) { 362 | p5._validateParameters('createA', arguments); 363 | var elt = document.createElement('a'); 364 | elt.href = href; 365 | elt.innerHTML = html; 366 | if (target) elt.target = target; 367 | return addElement(elt, this); 368 | }; 369 | 370 | /** INPUT **/ 371 | 372 | /** 373 | * Creates a slider <input></input> element in the DOM. 374 | * Use .size() to set the display length of the slider. 375 | * Appends to the container node if one is specified, otherwise 376 | * appends to body. 377 | * 378 | * @method createSlider 379 | * @param {Number} min minimum value of the slider 380 | * @param {Number} max maximum value of the slider 381 | * @param {Number} [value] default value of the slider 382 | * @param {Number} [step] step size for each tick of the slider (if step is set to 0, the slider will move continuously from the minimum to the maximum value) 383 | * @return {Object|p5.Element} pointer to p5.Element holding created node 384 | * @example 385 | *
386 | * var slider; 387 | * function setup() { 388 | * slider = createSlider(0, 255, 100); 389 | * slider.position(10, 10); 390 | * slider.style('width', '80px'); 391 | * } 392 | * 393 | * function draw() { 394 | * var val = slider.value(); 395 | * background(val); 396 | * } 397 | *
398 | * 399 | *
400 | * var slider; 401 | * function setup() { 402 | * colorMode(HSB); 403 | * slider = createSlider(0, 360, 60, 40); 404 | * slider.position(10, 10); 405 | * slider.style('width', '80px'); 406 | * } 407 | * 408 | * function draw() { 409 | * var val = slider.value(); 410 | * background(val, 100, 100, 1); 411 | * } 412 | *
413 | */ 414 | p5.prototype.createSlider = function(min, max, value, step) { 415 | p5._validateParameters('createSlider', arguments); 416 | var elt = document.createElement('input'); 417 | elt.type = 'range'; 418 | elt.min = min; 419 | elt.max = max; 420 | if (step === 0) { 421 | elt.step = 0.000000000000000001; // smallest valid step 422 | } else if (step) { 423 | elt.step = step; 424 | } 425 | if (typeof value === 'number') elt.value = value; 426 | return addElement(elt, this); 427 | }; 428 | 429 | /** 430 | * Creates a <button></button> element in the DOM. 431 | * Use .size() to set the display size of the button. 432 | * Use .mousePressed() to specify behavior on press. 433 | * Appends to the container node if one is specified, otherwise 434 | * appends to body. 435 | * 436 | * @method createButton 437 | * @param {String} label label displayed on the button 438 | * @param {String} [value] value of the button 439 | * @return {Object|p5.Element} pointer to p5.Element holding created node 440 | * @example 441 | *
442 | * var button; 443 | * function setup() { 444 | * createCanvas(100, 100); 445 | * background(0); 446 | * button = createButton('click me'); 447 | * button.position(19, 19); 448 | * button.mousePressed(changeBG); 449 | * } 450 | * 451 | * function changeBG() { 452 | * var val = random(255); 453 | * background(val); 454 | * } 455 | *
456 | */ 457 | p5.prototype.createButton = function(label, value) { 458 | p5._validateParameters('createButton', arguments); 459 | var elt = document.createElement('button'); 460 | elt.innerHTML = label; 461 | if (value) elt.value = value; 462 | return addElement(elt, this); 463 | }; 464 | 465 | /** 466 | * Creates a checkbox <input></input> element in the DOM. 467 | * Calling .checked() on a checkbox returns if it is checked or not 468 | * 469 | * @method createCheckbox 470 | * @param {String} [label] label displayed after checkbox 471 | * @param {boolean} [value] value of the checkbox; checked is true, unchecked is false 472 | * @return {Object|p5.Element} pointer to p5.Element holding created node 473 | * @example 474 | *
475 | * var checkbox; 476 | * 477 | * function setup() { 478 | * checkbox = createCheckbox('label', false); 479 | * checkbox.changed(myCheckedEvent); 480 | * } 481 | * 482 | * function myCheckedEvent() { 483 | * if (this.checked()) { 484 | * console.log('Checking!'); 485 | * } else { 486 | * console.log('Unchecking!'); 487 | * } 488 | * } 489 | *
490 | */ 491 | p5.prototype.createCheckbox = function() { 492 | p5._validateParameters('createCheckbox', arguments); 493 | var elt = document.createElement('div'); 494 | var checkbox = document.createElement('input'); 495 | checkbox.type = 'checkbox'; 496 | elt.appendChild(checkbox); 497 | //checkbox must be wrapped in p5.Element before label so that label appears after 498 | var self = addElement(elt, this); 499 | self.checked = function() { 500 | var cb = self.elt.getElementsByTagName('input')[0]; 501 | if (cb) { 502 | if (arguments.length === 0) { 503 | return cb.checked; 504 | } else if (arguments[0]) { 505 | cb.checked = true; 506 | } else { 507 | cb.checked = false; 508 | } 509 | } 510 | return self; 511 | }; 512 | this.value = function(val) { 513 | self.value = val; 514 | return this; 515 | }; 516 | if (arguments[0]) { 517 | var ran = Math.random() 518 | .toString(36) 519 | .slice(2); 520 | var label = document.createElement('label'); 521 | checkbox.setAttribute('id', ran); 522 | label.htmlFor = ran; 523 | self.value(arguments[0]); 524 | label.appendChild(document.createTextNode(arguments[0])); 525 | elt.appendChild(label); 526 | } 527 | if (arguments[1]) { 528 | checkbox.checked = true; 529 | } 530 | return self; 531 | }; 532 | 533 | /** 534 | * Creates a dropdown menu <select></select> element in the DOM. 535 | * It also helps to assign select-box methods to p5.Element when selecting existing select box 536 | * @method createSelect 537 | * @param {boolean} [multiple] true if dropdown should support multiple selections 538 | * @return {p5.Element} 539 | * @example 540 | *
541 | * var sel; 542 | * 543 | * function setup() { 544 | * textAlign(CENTER); 545 | * background(200); 546 | * sel = createSelect(); 547 | * sel.position(10, 10); 548 | * sel.option('pear'); 549 | * sel.option('kiwi'); 550 | * sel.option('grape'); 551 | * sel.changed(mySelectEvent); 552 | * } 553 | * 554 | * function mySelectEvent() { 555 | * var item = sel.value(); 556 | * background(200); 557 | * text('it is a' + item + '!', 50, 50); 558 | * } 559 | *
560 | */ 561 | /** 562 | * @method createSelect 563 | * @param {Object} existing DOM select element 564 | * @return {p5.Element} 565 | */ 566 | 567 | p5.prototype.createSelect = function() { 568 | p5._validateParameters('createSelect', arguments); 569 | var elt, self; 570 | var arg = arguments[0]; 571 | if (typeof arg === 'object' && arg.elt.nodeName === 'SELECT') { 572 | self = arg; 573 | elt = this.elt = arg.elt; 574 | } else { 575 | elt = document.createElement('select'); 576 | if (arg && typeof arg === 'boolean') { 577 | elt.setAttribute('multiple', 'true'); 578 | } 579 | self = addElement(elt, this); 580 | } 581 | self.option = function(name, value) { 582 | var index; 583 | //see if there is already an option with this name 584 | for (var i = 0; i < this.elt.length; i++) { 585 | if (this.elt[i].innerHTML === name) { 586 | index = i; 587 | break; 588 | } 589 | } 590 | //if there is an option with this name we will modify it 591 | if (index !== undefined) { 592 | //if the user passed in false then delete that option 593 | if (value === false) { 594 | this.elt.remove(index); 595 | } else { 596 | //otherwise if the name and value are the same then change both 597 | if (this.elt[index].innerHTML === this.elt[index].value) { 598 | this.elt[index].innerHTML = this.elt[index].value = value; 599 | //otherwise just change the value 600 | } else { 601 | this.elt[index].value = value; 602 | } 603 | } 604 | } else { 605 | //if it doesn't exist make it 606 | var opt = document.createElement('option'); 607 | opt.innerHTML = name; 608 | if (arguments.length > 1) opt.value = value; 609 | else opt.value = name; 610 | elt.appendChild(opt); 611 | } 612 | }; 613 | self.selected = function(value) { 614 | var arr = [], 615 | i; 616 | if (arguments.length > 0) { 617 | for (i = 0; i < this.elt.length; i++) { 618 | if (value.toString() === this.elt[i].value) { 619 | this.elt.selectedIndex = i; 620 | } 621 | } 622 | return this; 623 | } else { 624 | if (this.elt.getAttribute('multiple')) { 625 | for (i = 0; i < this.elt.selectedOptions.length; i++) { 626 | arr.push(this.elt.selectedOptions[i].value); 627 | } 628 | return arr; 629 | } else { 630 | return this.elt.value; 631 | } 632 | } 633 | }; 634 | return self; 635 | }; 636 | 637 | /** 638 | * Creates a radio button <input></input> element in the DOM. 639 | * The .option() method can be used to set options for the radio after it is 640 | * created. The .value() method will return the currently selected option. 641 | * 642 | * @method createRadio 643 | * @param {String} [divId] the id and name of the created div and input field respectively 644 | * @return {Object|p5.Element} pointer to p5.Element holding created node 645 | * @example 646 | *
647 | * var radio; 648 | * 649 | * function setup() { 650 | * radio = createRadio(); 651 | * radio.option('black'); 652 | * radio.option('white'); 653 | * radio.option('gray'); 654 | * radio.style('width', '60px'); 655 | * textAlign(CENTER); 656 | * fill(255, 0, 0); 657 | * } 658 | * 659 | * function draw() { 660 | * var val = radio.value(); 661 | * background(val); 662 | * text(val, width / 2, height / 2); 663 | * } 664 | *
665 | *
666 | * var radio; 667 | * 668 | * function setup() { 669 | * radio = createRadio(); 670 | * radio.option('apple', 1); 671 | * radio.option('bread', 2); 672 | * radio.option('juice', 3); 673 | * radio.style('width', '60px'); 674 | * textAlign(CENTER); 675 | * } 676 | * 677 | * function draw() { 678 | * background(200); 679 | * var val = radio.value(); 680 | * if (val) { 681 | * text('item cost is $' + val, width / 2, height / 2); 682 | * } 683 | * } 684 | *
685 | */ 686 | p5.prototype.createRadio = function(existing_radios) { 687 | p5._validateParameters('createRadio', arguments); 688 | // do some prep by counting number of radios on page 689 | var radios = document.querySelectorAll('input[type=radio]'); 690 | var count = 0; 691 | if (radios.length > 1) { 692 | var length = radios.length; 693 | var prev = radios[0].name; 694 | var current = radios[1].name; 695 | count = 1; 696 | for (var i = 1; i < length; i++) { 697 | current = radios[i].name; 698 | if (prev !== current) { 699 | count++; 700 | } 701 | prev = current; 702 | } 703 | } else if (radios.length === 1) { 704 | count = 1; 705 | } 706 | // see if we got an existing set of radios from callee 707 | var elt, self; 708 | if (typeof existing_radios === 'object') { 709 | // use existing elements 710 | self = existing_radios; 711 | elt = this.elt = existing_radios.elt; 712 | } else { 713 | // create a set of radio buttons 714 | elt = document.createElement('div'); 715 | self = addElement(elt, this); 716 | } 717 | // setup member functions 718 | self._getInputChildrenArray = function() { 719 | return Array.prototype.slice.call(this.elt.children).filter(function(c) { 720 | return c.tagName === 'INPUT'; 721 | }); 722 | }; 723 | 724 | var times = -1; 725 | self.option = function(name, value) { 726 | var opt = document.createElement('input'); 727 | opt.type = 'radio'; 728 | opt.innerHTML = name; 729 | if (value) opt.value = value; 730 | else opt.value = name; 731 | opt.setAttribute('name', 'defaultradio' + count); 732 | elt.appendChild(opt); 733 | if (name) { 734 | times++; 735 | var label = document.createElement('label'); 736 | opt.setAttribute('id', 'defaultradio' + count + '-' + times); 737 | label.htmlFor = 'defaultradio' + count + '-' + times; 738 | label.appendChild(document.createTextNode(name)); 739 | elt.appendChild(label); 740 | } 741 | return opt; 742 | }; 743 | self.selected = function(value) { 744 | var i; 745 | var inputChildren = self._getInputChildrenArray(); 746 | if (value) { 747 | for (i = 0; i < inputChildren.length; i++) { 748 | if (inputChildren[i].value === value) inputChildren[i].checked = true; 749 | } 750 | return this; 751 | } else { 752 | for (i = 0; i < inputChildren.length; i++) { 753 | if (inputChildren[i].checked === true) return inputChildren[i].value; 754 | } 755 | } 756 | }; 757 | self.value = function(value) { 758 | var i; 759 | var inputChildren = self._getInputChildrenArray(); 760 | if (value) { 761 | for (i = 0; i < inputChildren.length; i++) { 762 | if (inputChildren[i].value === value) inputChildren[i].checked = true; 763 | } 764 | return this; 765 | } else { 766 | for (i = 0; i < inputChildren.length; i++) { 767 | if (inputChildren[i].checked === true) return inputChildren[i].value; 768 | } 769 | return ''; 770 | } 771 | }; 772 | return self; 773 | }; 774 | 775 | /** 776 | * Creates an <input></input> element in the DOM for text input. 777 | * Use .size() to set the display length of the box. 778 | * Appends to the container node if one is specified, otherwise 779 | * appends to body. 780 | * 781 | * @method createInput 782 | * @param {String} [value] default value of the input box 783 | * @param {String} [type] type of text, ie text, password etc. Defaults to text 784 | * @return {Object|p5.Element} pointer to p5.Element holding created node 785 | * @example 786 | *
787 | * function setup() { 788 | * var inp = createInput(''); 789 | * inp.input(myInputEvent); 790 | * } 791 | * 792 | * function myInputEvent() { 793 | * console.log('you are typing: ', this.value()); 794 | * } 795 | *
796 | */ 797 | p5.prototype.createInput = function(value, type) { 798 | p5._validateParameters('createInput', arguments); 799 | var elt = document.createElement('input'); 800 | elt.type = type ? type : 'text'; 801 | if (value) elt.value = value; 802 | return addElement(elt, this); 803 | }; 804 | 805 | /** 806 | * Creates an <input></input> element in the DOM of type 'file'. 807 | * This allows users to select local files for use in a sketch. 808 | * 809 | * @method createFileInput 810 | * @param {Function} [callback] callback function for when a file loaded 811 | * @param {String} [multiple] optional to allow multiple files selected 812 | * @return {Object|p5.Element} pointer to p5.Element holding created DOM element 813 | * @example 814 | * var input; 815 | * var img; 816 | * 817 | * function setup() { 818 | * input = createFileInput(handleFile); 819 | * input.position(0, 0); 820 | * } 821 | * 822 | * function draw() { 823 | * if (img) { 824 | * image(img, 0, 0, width, height); 825 | * } 826 | * } 827 | * 828 | * function handleFile(file) { 829 | * print(file); 830 | * if (file.type === 'image') { 831 | * img = createImg(file.data); 832 | * img.hide(); 833 | * } 834 | * } 835 | */ 836 | p5.prototype.createFileInput = function(callback, multiple) { 837 | p5._validateParameters('createFileInput', arguments); 838 | // Function to handle when a file is selected 839 | // We're simplifying life and assuming that we always 840 | // want to load every selected file 841 | function handleFileSelect(evt) { 842 | function makeLoader(theFile) { 843 | // Making a p5.File object 844 | var p5file = new p5.File(theFile); 845 | return function(e) { 846 | p5file.data = e.target.result; 847 | callback(p5file); 848 | }; 849 | } 850 | // These are the files 851 | var files = evt.target.files; 852 | // Load each one and trigger a callback 853 | for (var i = 0; i < files.length; i++) { 854 | var f = files[i]; 855 | var reader = new FileReader(); 856 | 857 | reader.onload = makeLoader(f); 858 | 859 | // Text or data? 860 | // This should likely be improved 861 | if (f.type.indexOf('text') > -1) { 862 | reader.readAsText(f); 863 | } else { 864 | reader.readAsDataURL(f); 865 | } 866 | } 867 | } 868 | // Is the file stuff supported? 869 | if (window.File && window.FileReader && window.FileList && window.Blob) { 870 | // Yup, we're ok and make an input file selector 871 | var elt = document.createElement('input'); 872 | elt.type = 'file'; 873 | 874 | // If we get a second argument that evaluates to true 875 | // then we are looking for multiple files 876 | if (multiple) { 877 | // Anything gets the job done 878 | elt.multiple = 'multiple'; 879 | } 880 | 881 | // Now let's handle when a file was selected 882 | elt.addEventListener('change', handleFileSelect, false); 883 | return addElement(elt, this); 884 | } else { 885 | console.log( 886 | 'The File APIs are not fully supported in this browser. Cannot create element.' 887 | ); 888 | } 889 | }; 890 | 891 | /** VIDEO STUFF **/ 892 | 893 | function createMedia(pInst, type, src, callback) { 894 | var elt = document.createElement(type); 895 | 896 | // allow src to be empty 897 | src = src || ''; 898 | if (typeof src === 'string') { 899 | src = [src]; 900 | } 901 | for (var i = 0; i < src.length; i++) { 902 | var source = document.createElement('source'); 903 | source.src = src[i]; 904 | elt.appendChild(source); 905 | } 906 | if (typeof callback !== 'undefined') { 907 | var callbackHandler = function() { 908 | callback(); 909 | elt.removeEventListener('canplaythrough', callbackHandler); 910 | }; 911 | elt.addEventListener('canplaythrough', callbackHandler); 912 | } 913 | 914 | var c = addElement(elt, pInst, true); 915 | c.loadedmetadata = false; 916 | // set width and height onload metadata 917 | elt.addEventListener('loadedmetadata', function() { 918 | c.width = elt.videoWidth; 919 | c.height = elt.videoHeight; 920 | // set elt width and height if not set 921 | if (c.elt.width === 0) c.elt.width = elt.videoWidth; 922 | if (c.elt.height === 0) c.elt.height = elt.videoHeight; 923 | c.loadedmetadata = true; 924 | }); 925 | 926 | return c; 927 | } 928 | /** 929 | * Creates an HTML5 <video> element in the DOM for simple playback 930 | * of audio/video. Shown by default, can be hidden with .hide() 931 | * and drawn into canvas using video(). Appends to the container 932 | * node if one is specified, otherwise appends to body. The first parameter 933 | * can be either a single string path to a video file, or an array of string 934 | * paths to different formats of the same video. This is useful for ensuring 935 | * that your video can play across different browsers, as each supports 936 | * different formats. See this 937 | * page for further information about supported formats. 938 | * 939 | * @method createVideo 940 | * @param {String|Array} src path to a video file, or array of paths for 941 | * supporting different browsers 942 | * @param {Object} [callback] callback function to be called upon 943 | * 'canplaythrough' event fire, that is, when the 944 | * browser can play the media, and estimates that 945 | * enough data has been loaded to play the media 946 | * up to its end without having to stop for 947 | * further buffering of content 948 | * @return {p5.MediaElement|p5.Element} pointer to video p5.Element 949 | * @example 950 | *
951 | * var vid; 952 | * function setup() { 953 | * vid = createVideo(['small.mp4', 'small.ogv', 'small.webm'], vidLoad); 954 | * } 955 | * 956 | * // This function is called when the video loads 957 | * function vidLoad() { 958 | * vid.play(); 959 | * } 960 | *
961 | */ 962 | p5.prototype.createVideo = function(src, callback) { 963 | p5._validateParameters('createVideo', arguments); 964 | return createMedia(this, 'video', src, callback); 965 | }; 966 | 967 | /** AUDIO STUFF **/ 968 | 969 | /** 970 | * Creates a hidden HTML5 <audio> element in the DOM for simple audio 971 | * playback. Appends to the container node if one is specified, 972 | * otherwise appends to body. The first parameter 973 | * can be either a single string path to a audio file, or an array of string 974 | * paths to different formats of the same audio. This is useful for ensuring 975 | * that your audio can play across different browsers, as each supports 976 | * different formats. See this 977 | * page for further information about supported formats. 978 | * 979 | * @method createAudio 980 | * @param {String|String[]} [src] path to an audio file, or array of paths 981 | * for supporting different browsers 982 | * @param {Object} [callback] callback function to be called upon 983 | * 'canplaythrough' event fire, that is, when the 984 | * browser can play the media, and estimates that 985 | * enough data has been loaded to play the media 986 | * up to its end without having to stop for 987 | * further buffering of content 988 | * @return {p5.MediaElement|p5.Element} pointer to audio p5.Element /** 989 | * @example 990 | *
991 | * var ele; 992 | * function setup() { 993 | * ele = createAudio('assets/beat.mp3'); 994 | * 995 | * // here we set the element to autoplay 996 | * // The element will play as soon 997 | * // as it is able to do so. 998 | * ele.autoplay(true); 999 | * } 1000 | *
1001 | */ 1002 | p5.prototype.createAudio = function(src, callback) { 1003 | p5._validateParameters('createAudio', arguments); 1004 | return createMedia(this, 'audio', src, callback); 1005 | }; 1006 | 1007 | /** CAMERA STUFF **/ 1008 | 1009 | p5.prototype.VIDEO = 'video'; 1010 | p5.prototype.AUDIO = 'audio'; 1011 | 1012 | // from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 1013 | // Older browsers might not implement mediaDevices at all, so we set an empty object first 1014 | if (navigator.mediaDevices === undefined) { 1015 | navigator.mediaDevices = {}; 1016 | } 1017 | 1018 | // Some browsers partially implement mediaDevices. We can't just assign an object 1019 | // with getUserMedia as it would overwrite existing properties. 1020 | // Here, we will just add the getUserMedia property if it's missing. 1021 | if (navigator.mediaDevices.getUserMedia === undefined) { 1022 | navigator.mediaDevices.getUserMedia = function(constraints) { 1023 | // First get ahold of the legacy getUserMedia, if present 1024 | var getUserMedia = 1025 | navigator.webkitGetUserMedia || navigator.mozGetUserMedia; 1026 | 1027 | // Some browsers just don't implement it - return a rejected promise with an error 1028 | // to keep a consistent interface 1029 | if (!getUserMedia) { 1030 | return Promise.reject( 1031 | new Error('getUserMedia is not implemented in this browser') 1032 | ); 1033 | } 1034 | 1035 | // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise 1036 | return new Promise(function(resolve, reject) { 1037 | getUserMedia.call(navigator, constraints, resolve, reject); 1038 | }); 1039 | }; 1040 | } 1041 | 1042 | /** 1043 | *

Creates a new <video> element that contains the audio/video feed 1044 | * from a webcam. This can be drawn onto the canvas using video().

1045 | *

More specific properties of the feed can be passing in a Constraints object. 1046 | * See the 1047 | * W3C 1048 | * spec for possible properties. Note that not all of these are supported 1049 | * by all browsers.

1050 | *

Security note: A new browser security specification requires that getUserMedia, 1051 | * which is behind createCapture(), only works when you're running the code locally, 1052 | * or on HTTPS. Learn more here 1053 | * and here.

1054 | * 1055 | * @method createCapture 1056 | * @param {String|Constant|Object} type type of capture, either VIDEO or 1057 | * AUDIO if none specified, default both, 1058 | * or a Constraints object 1059 | * @param {Function} callback function to be called once 1060 | * stream has loaded 1061 | * @return {Object|p5.Element} capture video p5.Element 1062 | * @example 1063 | *
1064 | * var capture; 1065 | * 1066 | * function setup() { 1067 | * createCanvas(480, 120); 1068 | * capture = createCapture(VIDEO); 1069 | * } 1070 | * 1071 | * function draw() { 1072 | * image(capture, 0, 0, width, width * capture.height / capture.width); 1073 | * filter(INVERT); 1074 | * } 1075 | *
1076 | *
1077 | * function setup() { 1078 | * createCanvas(480, 120); 1079 | * var constraints = { 1080 | * video: { 1081 | * mandatory: { 1082 | * minWidth: 1280, 1083 | * minHeight: 720 1084 | * }, 1085 | * optional: [{ maxFrameRate: 10 }] 1086 | * }, 1087 | * audio: true 1088 | * }; 1089 | * createCapture(constraints, function(stream) { 1090 | * console.log(stream); 1091 | * }); 1092 | * } 1093 | *
1094 | */ 1095 | p5.prototype.createCapture = function() { 1096 | p5._validateParameters('createCapture', arguments); 1097 | var useVideo = true; 1098 | var useAudio = true; 1099 | var constraints; 1100 | var cb; 1101 | for (var i = 0; i < arguments.length; i++) { 1102 | if (arguments[i] === p5.prototype.VIDEO) { 1103 | useAudio = false; 1104 | } else if (arguments[i] === p5.prototype.AUDIO) { 1105 | useVideo = false; 1106 | } else if (typeof arguments[i] === 'object') { 1107 | constraints = arguments[i]; 1108 | } else if (typeof arguments[i] === 'function') { 1109 | cb = arguments[i]; 1110 | } 1111 | } 1112 | if (navigator.getUserMedia) { 1113 | var elt = document.createElement('video'); 1114 | 1115 | if (!constraints) { 1116 | constraints = { video: useVideo, audio: useAudio }; 1117 | } 1118 | 1119 | navigator.mediaDevices.getUserMedia(constraints).then( 1120 | function(stream) { 1121 | try { 1122 | if ('srcObject' in elt) { 1123 | elt.srcObject = stream; 1124 | } else { 1125 | elt.src = window.URL.createObjectURL(stream); 1126 | } 1127 | } catch (err) { 1128 | elt.src = stream; 1129 | } 1130 | if (cb) { 1131 | cb(stream); 1132 | } 1133 | }, 1134 | function(e) { 1135 | console.log(e); 1136 | } 1137 | ); 1138 | } else { 1139 | throw 'getUserMedia not supported in this browser'; 1140 | } 1141 | var c = addElement(elt, this, true); 1142 | c.loadedmetadata = false; 1143 | // set width and height onload metadata 1144 | elt.addEventListener('loadedmetadata', function() { 1145 | elt.play(); 1146 | if (elt.width) { 1147 | c.width = elt.videoWidth = elt.width; 1148 | c.height = elt.videoHeight = elt.height; 1149 | } else { 1150 | c.width = c.elt.width = elt.videoWidth; 1151 | c.height = c.elt.height = elt.videoHeight; 1152 | } 1153 | c.loadedmetadata = true; 1154 | }); 1155 | return c; 1156 | }; 1157 | 1158 | /** 1159 | * Creates element with given tag in the DOM with given content. 1160 | * Appends to the container node if one is specified, otherwise 1161 | * appends to body. 1162 | * 1163 | * @method createElement 1164 | * @param {String} tag tag for the new element 1165 | * @param {String} [content] html content to be inserted into the element 1166 | * @return {Object|p5.Element} pointer to p5.Element holding created node 1167 | * @example 1168 | *
1169 | * createElement('h2', 'im an h2 p5.element!'); 1170 | *
1171 | */ 1172 | p5.prototype.createElement = function(tag, content) { 1173 | p5._validateParameters('createElement', arguments); 1174 | var elt = document.createElement(tag); 1175 | if (typeof content !== 'undefined') { 1176 | elt.innerHTML = content; 1177 | } 1178 | return addElement(elt, this); 1179 | }; 1180 | 1181 | // ============================================================================= 1182 | // p5.Element additions 1183 | // ============================================================================= 1184 | /** 1185 | * 1186 | * Adds specified class to the element. 1187 | * 1188 | * @for p5.Element 1189 | * @method addClass 1190 | * @param {String} class name of class to add 1191 | * @return {Object|p5.Element} 1192 | * @example 1193 | *
1194 | * var div = createDiv('div'); 1195 | * div.addClass('myClass'); 1196 | *
1197 | */ 1198 | p5.Element.prototype.addClass = function(c) { 1199 | if (this.elt.className) { 1200 | // PEND don't add class more than once 1201 | //var regex = new RegExp('[^a-zA-Z\d:]?'+c+'[^a-zA-Z\d:]?'); 1202 | //if (this.elt.className.search(/[^a-zA-Z\d:]?hi[^a-zA-Z\d:]?/) === -1) { 1203 | this.elt.className = this.elt.className + ' ' + c; 1204 | //} 1205 | } else { 1206 | this.elt.className = c; 1207 | } 1208 | return this; 1209 | }; 1210 | 1211 | /** 1212 | * 1213 | * Removes specified class from the element. 1214 | * 1215 | * @method removeClass 1216 | * @param {String} class name of class to remove 1217 | * @return {Object|p5.Element} * @example 1218 | *
1219 | * // In this example, a class is set when the div is created 1220 | * // and removed when mouse is pressed. This could link up 1221 | * // with a CSS style rule to toggle style properties. 1222 | * 1223 | * var div; 1224 | * 1225 | * function setup() { 1226 | * div = createDiv('div'); 1227 | * div.addClass('myClass'); 1228 | * } 1229 | * 1230 | * function mousePressed() { 1231 | * div.removeClass('myClass'); 1232 | * } 1233 | *
1234 | */ 1235 | p5.Element.prototype.removeClass = function(c) { 1236 | var regex = new RegExp('(?:^|\\s)' + c + '(?!\\S)'); 1237 | this.elt.className = this.elt.className.replace(regex, ''); 1238 | this.elt.className = this.elt.className.replace(/^\s+|\s+$/g, ''); //prettify (optional) 1239 | return this; 1240 | }; 1241 | 1242 | /** 1243 | * 1244 | * Attaches the element as a child to the parent specified. 1245 | * Accepts either a string ID, DOM node, or p5.Element. 1246 | * If no argument is specified, an array of children DOM nodes is returned. 1247 | * 1248 | * @method child 1249 | * @param {String|Object|p5.Element} [child] the ID, DOM node, or p5.Element 1250 | * to add to the current element 1251 | * @return {p5.Element} 1252 | * @example 1253 | *
1254 | * var div0 = createDiv('this is the parent'); 1255 | * var div1 = createDiv('this is the child'); 1256 | * div0.child(div1); // use p5.Element 1257 | *
1258 | *
1259 | * var div0 = createDiv('this is the parent'); 1260 | * var div1 = createDiv('this is the child'); 1261 | * div1.id('apples'); 1262 | * div0.child('apples'); // use id 1263 | *
1264 | *
1265 | * var div0 = createDiv('this is the parent'); 1266 | * var elt = document.getElementById('myChildDiv'); 1267 | * div0.child(elt); // use element from page 1268 | *
1269 | */ 1270 | p5.Element.prototype.child = function(c) { 1271 | if (typeof c === 'undefined') { 1272 | return this.elt.childNodes; 1273 | } 1274 | if (typeof c === 'string') { 1275 | if (c[0] === '#') { 1276 | c = c.substring(1); 1277 | } 1278 | c = document.getElementById(c); 1279 | } else if (c instanceof p5.Element) { 1280 | c = c.elt; 1281 | } 1282 | this.elt.appendChild(c); 1283 | return this; 1284 | }; 1285 | 1286 | /** 1287 | * Centers a p5 Element either vertically, horizontally, 1288 | * or both, relative to its parent or according to 1289 | * the body if the Element has no parent. If no argument is passed 1290 | * the Element is aligned both vertically and horizontally. 1291 | * 1292 | * @method center 1293 | * @param {String} [align] passing 'vertical', 'horizontal' aligns element accordingly 1294 | * @return {Object|p5.Element} pointer to p5.Element 1295 | * @example 1296 | *
1297 | * function setup() { 1298 | * var div = createDiv('').size(10, 10); 1299 | * div.style('background-color', 'orange'); 1300 | * div.center(); 1301 | * } 1302 | *
1303 | */ 1304 | p5.Element.prototype.center = function(align) { 1305 | var style = this.elt.style.display; 1306 | var hidden = this.elt.style.display === 'none'; 1307 | var parentHidden = this.parent().style.display === 'none'; 1308 | var pos = { x: this.elt.offsetLeft, y: this.elt.offsetTop }; 1309 | 1310 | if (hidden) this.show(); 1311 | 1312 | this.elt.style.display = 'block'; 1313 | this.position(0, 0); 1314 | 1315 | if (parentHidden) this.parent().style.display = 'block'; 1316 | 1317 | var wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth); 1318 | var hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight); 1319 | var y = pos.y; 1320 | var x = pos.x; 1321 | 1322 | if (align === 'both' || align === undefined) { 1323 | this.position(wOffset / 2, hOffset / 2); 1324 | } else if (align === 'horizontal') { 1325 | this.position(wOffset / 2, y); 1326 | } else if (align === 'vertical') { 1327 | this.position(x, hOffset / 2); 1328 | } 1329 | 1330 | this.style('display', style); 1331 | 1332 | if (hidden) this.hide(); 1333 | 1334 | if (parentHidden) this.parent().style.display = 'none'; 1335 | 1336 | return this; 1337 | }; 1338 | 1339 | /** 1340 | * 1341 | * If an argument is given, sets the inner HTML of the element, 1342 | * replacing any existing html. If true is included as a second 1343 | * argument, html is appended instead of replacing existing html. 1344 | * If no arguments are given, returns 1345 | * the inner HTML of the element. 1346 | * 1347 | * @for p5.Element 1348 | * @method html 1349 | * @param {String} [html] the HTML to be placed inside the element 1350 | * @param {boolean} [append] whether to append HTML to existing 1351 | * @return {Object|p5.Element|String} 1352 | * @example 1353 | *
1354 | * var div = createDiv('').size(100, 100); 1355 | * div.html('hi'); 1356 | *
1357 | *
1358 | * var div = createDiv('Hello ').size(100, 100); 1359 | * div.html('World', true); 1360 | *
1361 | */ 1362 | p5.Element.prototype.html = function() { 1363 | if (arguments.length === 0) { 1364 | return this.elt.innerHTML; 1365 | } else if (arguments[1]) { 1366 | this.elt.innerHTML += arguments[0]; 1367 | return this; 1368 | } else { 1369 | this.elt.innerHTML = arguments[0]; 1370 | return this; 1371 | } 1372 | }; 1373 | 1374 | /** 1375 | * 1376 | * Sets the position of the element relative to (0, 0) of the 1377 | * window. Essentially, sets position:absolute and left and top 1378 | * properties of style. If no arguments given returns the x and y position 1379 | * of the element in an object. 1380 | * 1381 | * @method position 1382 | * @param {Number} [x] x-position relative to upper left of window 1383 | * @param {Number} [y] y-position relative to upper left of window 1384 | * @return {Object|p5.Element} 1385 | * @example 1386 | *
1387 | * function setup() { 1388 | * var cnv = createCanvas(100, 100); 1389 | * // positions canvas 50px to the right and 100px 1390 | * // below upper left corner of the window 1391 | * cnv.position(50, 100); 1392 | * } 1393 | *
1394 | */ 1395 | p5.Element.prototype.position = function() { 1396 | if (arguments.length === 0) { 1397 | return { x: this.elt.offsetLeft, y: this.elt.offsetTop }; 1398 | } else { 1399 | this.elt.style.position = 'absolute'; 1400 | this.elt.style.left = arguments[0] + 'px'; 1401 | this.elt.style.top = arguments[1] + 'px'; 1402 | this.x = arguments[0]; 1403 | this.y = arguments[1]; 1404 | return this; 1405 | } 1406 | }; 1407 | 1408 | /* Helper method called by p5.Element.style() */ 1409 | p5.Element.prototype._translate = function() { 1410 | this.elt.style.position = 'absolute'; 1411 | // save out initial non-translate transform styling 1412 | var transform = ''; 1413 | if (this.elt.style.transform) { 1414 | transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, ''); 1415 | transform = transform.replace(/translate[X-Z]?\(.*\)/g, ''); 1416 | } 1417 | if (arguments.length === 2) { 1418 | this.elt.style.transform = 1419 | 'translate(' + arguments[0] + 'px, ' + arguments[1] + 'px)'; 1420 | } else if (arguments.length > 2) { 1421 | this.elt.style.transform = 1422 | 'translate3d(' + 1423 | arguments[0] + 1424 | 'px,' + 1425 | arguments[1] + 1426 | 'px,' + 1427 | arguments[2] + 1428 | 'px)'; 1429 | if (arguments.length === 3) { 1430 | this.elt.parentElement.style.perspective = '1000px'; 1431 | } else { 1432 | this.elt.parentElement.style.perspective = arguments[3] + 'px'; 1433 | } 1434 | } 1435 | // add any extra transform styling back on end 1436 | this.elt.style.transform += transform; 1437 | return this; 1438 | }; 1439 | 1440 | /* Helper method called by p5.Element.style() */ 1441 | p5.Element.prototype._rotate = function() { 1442 | // save out initial non-rotate transform styling 1443 | var transform = ''; 1444 | if (this.elt.style.transform) { 1445 | transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, ''); 1446 | transform = transform.replace(/rotate[X-Z]?\(.*\)/g, ''); 1447 | } 1448 | 1449 | if (arguments.length === 1) { 1450 | this.elt.style.transform = 'rotate(' + arguments[0] + 'deg)'; 1451 | } else if (arguments.length === 2) { 1452 | this.elt.style.transform = 1453 | 'rotate(' + arguments[0] + 'deg, ' + arguments[1] + 'deg)'; 1454 | } else if (arguments.length === 3) { 1455 | this.elt.style.transform = 'rotateX(' + arguments[0] + 'deg)'; 1456 | this.elt.style.transform += 'rotateY(' + arguments[1] + 'deg)'; 1457 | this.elt.style.transform += 'rotateZ(' + arguments[2] + 'deg)'; 1458 | } 1459 | // add remaining transform back on 1460 | this.elt.style.transform += transform; 1461 | return this; 1462 | }; 1463 | 1464 | /** 1465 | * Sets the given style (css) property (1st arg) of the element with the 1466 | * given value (2nd arg). If a single argument is given, .style() 1467 | * returns the value of the given property; however, if the single argument 1468 | * is given in css syntax ('text-align:center'), .style() sets the css 1469 | * appropriatly. .style() also handles 2d and 3d css transforms. If 1470 | * the 1st arg is 'rotate', 'translate', or 'position', the following arguments 1471 | * accept Numbers as values. ('translate', 10, 100, 50); 1472 | * 1473 | * @method style 1474 | * @param {String} property property to be set 1475 | * @param {String|Number|p5.Color} [value] value to assign to property (only String|Number for rotate/translate) 1476 | * @param {String|Number|p5.Color} [value2] position can take a 2nd value 1477 | * @param {String|Number|p5.Color} [value3] translate can take a 2nd & 3rd value 1478 | * @return {String|Object|p5.Element} value of property, if no value is specified 1479 | * or p5.Element 1480 | * @example 1481 | *
1482 | * var myDiv = createDiv('I like pandas.'); 1483 | * myDiv.style('font-size', '18px'); 1484 | * myDiv.style('color', '#ff0000'); 1485 | *
1486 | *
1487 | * var col = color(25, 23, 200, 50); 1488 | * var button = createButton('button'); 1489 | * button.style('background-color', col); 1490 | * button.position(10, 10); 1491 | *
1492 | *
1493 | * var myDiv = createDiv('I like lizards.'); 1494 | * myDiv.style('position', 20, 20); 1495 | * myDiv.style('rotate', 45); 1496 | *
1497 | *
1498 | * var myDiv; 1499 | * function setup() { 1500 | * background(200); 1501 | * myDiv = createDiv('I like gray.'); 1502 | * myDiv.position(20, 20); 1503 | * } 1504 | * 1505 | * function draw() { 1506 | * myDiv.style('font-size', mouseX + 'px'); 1507 | * } 1508 | *
1509 | */ 1510 | p5.Element.prototype.style = function(prop, val) { 1511 | var self = this; 1512 | 1513 | if (val instanceof p5.Color) { 1514 | val = 1515 | 'rgba(' + 1516 | val.levels[0] + 1517 | ',' + 1518 | val.levels[1] + 1519 | ',' + 1520 | val.levels[2] + 1521 | ',' + 1522 | val.levels[3] / 255 + 1523 | ')'; 1524 | } 1525 | 1526 | if (typeof val === 'undefined') { 1527 | if (prop.indexOf(':') === -1) { 1528 | var styles = window.getComputedStyle(self.elt); 1529 | var style = styles.getPropertyValue(prop); 1530 | return style; 1531 | } else { 1532 | var attrs = prop.split(';'); 1533 | for (var i = 0; i < attrs.length; i++) { 1534 | var parts = attrs[i].split(':'); 1535 | if (parts[0] && parts[1]) { 1536 | this.elt.style[parts[0].trim()] = parts[1].trim(); 1537 | } 1538 | } 1539 | } 1540 | } else { 1541 | if (prop === 'rotate' || prop === 'translate' || prop === 'position') { 1542 | var trans = Array.prototype.shift.apply(arguments); 1543 | var f = this[trans] || this['_' + trans]; 1544 | f.apply(this, arguments); 1545 | } else { 1546 | this.elt.style[prop] = val; 1547 | if ( 1548 | prop === 'width' || 1549 | prop === 'height' || 1550 | prop === 'left' || 1551 | prop === 'top' 1552 | ) { 1553 | var numVal = val.replace(/\D+/g, ''); 1554 | this[prop] = parseInt(numVal, 10); // pend: is this necessary? 1555 | } 1556 | } 1557 | } 1558 | return this; 1559 | }; 1560 | 1561 | /** 1562 | * 1563 | * Adds a new attribute or changes the value of an existing attribute 1564 | * on the specified element. If no value is specified, returns the 1565 | * value of the given attribute, or null if attribute is not set. 1566 | * 1567 | * @method attribute 1568 | * @param {String} attr attribute to set 1569 | * @param {String} [value] value to assign to attribute 1570 | * @return {String|Object|p5.Element} value of attribute, if no value is 1571 | * specified or p5.Element 1572 | * @example 1573 | *
1574 | * var myDiv = createDiv('I like pandas.'); 1575 | * myDiv.attribute('align', 'center'); 1576 | *
1577 | */ 1578 | p5.Element.prototype.attribute = function(attr, value) { 1579 | //handling for checkboxes and radios to ensure options get 1580 | //attributes not divs 1581 | if ( 1582 | this.elt.firstChild != null && 1583 | (this.elt.firstChild.type === 'checkbox' || 1584 | this.elt.firstChild.type === 'radio') 1585 | ) { 1586 | if (typeof value === 'undefined') { 1587 | return this.elt.firstChild.getAttribute(attr); 1588 | } else { 1589 | for (var i = 0; i < this.elt.childNodes.length; i++) { 1590 | this.elt.childNodes[i].setAttribute(attr, value); 1591 | } 1592 | } 1593 | } else if (typeof value === 'undefined') { 1594 | return this.elt.getAttribute(attr); 1595 | } else { 1596 | this.elt.setAttribute(attr, value); 1597 | return this; 1598 | } 1599 | }; 1600 | 1601 | /** 1602 | * 1603 | * Removes an attribute on the specified element. 1604 | * 1605 | * @method removeAttribute 1606 | * @param {String} attr attribute to remove 1607 | * @return {Object|p5.Element} 1608 | * 1609 | * @example 1610 | *
1611 | * var button; 1612 | * var checkbox; 1613 | * 1614 | * function setup() { 1615 | * checkbox = createCheckbox('enable', true); 1616 | * checkbox.changed(enableButton); 1617 | * button = createButton('button'); 1618 | * button.position(10, 10); 1619 | * } 1620 | * 1621 | * function enableButton() { 1622 | * if (this.checked()) { 1623 | * // Re-enable the button 1624 | * button.removeAttribute('disabled'); 1625 | * } else { 1626 | * // Disable the button 1627 | * button.attribute('disabled', ''); 1628 | * } 1629 | * } 1630 | *
1631 | */ 1632 | p5.Element.prototype.removeAttribute = function(attr) { 1633 | if ( 1634 | this.elt.firstChild != null && 1635 | (this.elt.firstChild.type === 'checkbox' || 1636 | this.elt.firstChild.type === 'radio') 1637 | ) { 1638 | for (var i = 0; i < this.elt.childNodes.length; i++) { 1639 | this.elt.childNodes[i].removeAttribute(attr); 1640 | } 1641 | } 1642 | this.elt.removeAttribute(attr); 1643 | return this; 1644 | }; 1645 | 1646 | /** 1647 | * Either returns the value of the element if no arguments 1648 | * given, or sets the value of the element. 1649 | * 1650 | * @method value 1651 | * @param {String|Number} [value] 1652 | * @return {String|Object|p5.Element} value of element if no value is specified or p5.Element 1653 | * @example 1654 | *
1655 | * // gets the value 1656 | * var inp; 1657 | * function setup() { 1658 | * inp = createInput(''); 1659 | * } 1660 | * 1661 | * function mousePressed() { 1662 | * print(inp.value()); 1663 | * } 1664 | *
1665 | *
1666 | * // sets the value 1667 | * var inp; 1668 | * function setup() { 1669 | * inp = createInput('myValue'); 1670 | * } 1671 | * 1672 | * function mousePressed() { 1673 | * inp.value('myValue'); 1674 | * } 1675 | *
1676 | */ 1677 | p5.Element.prototype.value = function() { 1678 | if (arguments.length > 0) { 1679 | this.elt.value = arguments[0]; 1680 | return this; 1681 | } else { 1682 | if (this.elt.type === 'range') { 1683 | return parseFloat(this.elt.value); 1684 | } else return this.elt.value; 1685 | } 1686 | }; 1687 | 1688 | /** 1689 | * 1690 | * Shows the current element. Essentially, setting display:block for the style. 1691 | * 1692 | * @method show 1693 | * @return {Object|p5.Element} 1694 | * @example 1695 | *
1696 | * var div = createDiv('div'); 1697 | * div.style('display', 'none'); 1698 | * div.show(); // turns display to block 1699 | *
1700 | */ 1701 | p5.Element.prototype.show = function() { 1702 | this.elt.style.display = 'block'; 1703 | return this; 1704 | }; 1705 | 1706 | /** 1707 | * Hides the current element. Essentially, setting display:none for the style. 1708 | * 1709 | * @method hide 1710 | * @return {Object|p5.Element} 1711 | * @example 1712 | *
1713 | * var div = createDiv('this is a div'); 1714 | * div.hide(); 1715 | *
1716 | */ 1717 | p5.Element.prototype.hide = function() { 1718 | this.elt.style.display = 'none'; 1719 | return this; 1720 | }; 1721 | 1722 | /** 1723 | * 1724 | * Sets the width and height of the element. AUTO can be used to 1725 | * only adjust one dimension. If no arguments given returns the width and height 1726 | * of the element in an object. 1727 | * 1728 | * @method size 1729 | * @param {Number|Constant} [w] width of the element, either AUTO, or a number 1730 | * @param {Number|Constant} [h] height of the element, either AUTO, or a number 1731 | * @return {Object|p5.Element} 1732 | * @example 1733 | *
1734 | * var div = createDiv('this is a div'); 1735 | * div.size(100, 100); 1736 | *
1737 | */ 1738 | p5.Element.prototype.size = function(w, h) { 1739 | if (arguments.length === 0) { 1740 | return { width: this.elt.offsetWidth, height: this.elt.offsetHeight }; 1741 | } else { 1742 | var aW = w; 1743 | var aH = h; 1744 | var AUTO = p5.prototype.AUTO; 1745 | if (aW !== AUTO || aH !== AUTO) { 1746 | if (aW === AUTO) { 1747 | aW = h * this.width / this.height; 1748 | } else if (aH === AUTO) { 1749 | aH = w * this.height / this.width; 1750 | } 1751 | // set diff for cnv vs normal div 1752 | if (this.elt instanceof HTMLCanvasElement) { 1753 | var j = {}; 1754 | var k = this.elt.getContext('2d'); 1755 | var prop; 1756 | for (prop in k) { 1757 | j[prop] = k[prop]; 1758 | } 1759 | this.elt.setAttribute('width', aW * this._pInst._pixelDensity); 1760 | this.elt.setAttribute('height', aH * this._pInst._pixelDensity); 1761 | this.elt.setAttribute( 1762 | 'style', 1763 | 'width:' + aW + 'px; height:' + aH + 'px' 1764 | ); 1765 | this._pInst.scale( 1766 | this._pInst._pixelDensity, 1767 | this._pInst._pixelDensity 1768 | ); 1769 | for (prop in j) { 1770 | this.elt.getContext('2d')[prop] = j[prop]; 1771 | } 1772 | } else { 1773 | this.elt.style.width = aW + 'px'; 1774 | this.elt.style.height = aH + 'px'; 1775 | this.elt.width = aW; 1776 | this.elt.height = aH; 1777 | this.width = aW; 1778 | this.height = aH; 1779 | } 1780 | 1781 | this.width = this.elt.offsetWidth; 1782 | this.height = this.elt.offsetHeight; 1783 | 1784 | if (this._pInst) { 1785 | // main canvas associated with p5 instance 1786 | if (this._pInst._curElement.elt === this.elt) { 1787 | this._pInst._setProperty('width', this.elt.offsetWidth); 1788 | this._pInst._setProperty('height', this.elt.offsetHeight); 1789 | } 1790 | } 1791 | } 1792 | return this; 1793 | } 1794 | }; 1795 | 1796 | /** 1797 | * Removes the element and deregisters all listeners. 1798 | * @method remove 1799 | * @example 1800 | *
1801 | * var myDiv = createDiv('this is some text'); 1802 | * myDiv.remove(); 1803 | *
1804 | */ 1805 | p5.Element.prototype.remove = function() { 1806 | // deregister events 1807 | for (var ev in this._events) { 1808 | this.elt.removeEventListener(ev, this._events[ev]); 1809 | } 1810 | if (this.elt.parentNode) { 1811 | this.elt.parentNode.removeChild(this.elt); 1812 | } 1813 | delete this; 1814 | }; 1815 | 1816 | // ============================================================================= 1817 | // p5.MediaElement additions 1818 | // ============================================================================= 1819 | 1820 | /** 1821 | * Extends p5.Element to handle audio and video. In addition to the methods 1822 | * of p5.Element, it also contains methods for controlling media. It is not 1823 | * called directly, but p5.MediaElements are created by calling createVideo, 1824 | * createAudio, and createCapture. 1825 | * 1826 | * @class p5.MediaElement 1827 | * @constructor 1828 | * @param {String} elt DOM node that is wrapped 1829 | */ 1830 | p5.MediaElement = function(elt, pInst) { 1831 | p5.Element.call(this, elt, pInst); 1832 | 1833 | var self = this; 1834 | this.elt.crossOrigin = 'anonymous'; 1835 | 1836 | this._prevTime = 0; 1837 | this._cueIDCounter = 0; 1838 | this._cues = []; 1839 | this._pixelDensity = 1; 1840 | this._modified = false; 1841 | 1842 | /** 1843 | * Path to the media element source. 1844 | * 1845 | * @property src 1846 | * @return {String} src 1847 | * @example 1848 | *
1849 | * var ele; 1850 | * 1851 | * function setup() { 1852 | * background(250); 1853 | * 1854 | * //p5.MediaElement objects are usually created 1855 | * //by calling the createAudio(), createVideo(), 1856 | * //and createCapture() functions. 1857 | * 1858 | * //In this example we create 1859 | * //a new p5.MediaElement via createAudio(). 1860 | * ele = createAudio('assets/beat.mp3'); 1861 | * 1862 | * //We'll set up our example so that 1863 | * //when you click on the text, 1864 | * //an alert box displays the MediaElement's 1865 | * //src field. 1866 | * textAlign(CENTER); 1867 | * text('Click Me!', width / 2, height / 2); 1868 | * } 1869 | * 1870 | * function mouseClicked() { 1871 | * //here we test if the mouse is over the 1872 | * //canvas element when it's clicked 1873 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { 1874 | * //Show our p5.MediaElement's src field 1875 | * alert(ele.src); 1876 | * } 1877 | * } 1878 | *
1879 | */ 1880 | Object.defineProperty(self, 'src', { 1881 | get: function() { 1882 | var firstChildSrc = self.elt.children[0].src; 1883 | var srcVal = self.elt.src === window.location.href ? '' : self.elt.src; 1884 | var ret = 1885 | firstChildSrc === window.location.href ? srcVal : firstChildSrc; 1886 | return ret; 1887 | }, 1888 | set: function(newValue) { 1889 | for (var i = 0; i < self.elt.children.length; i++) { 1890 | self.elt.removeChild(self.elt.children[i]); 1891 | } 1892 | var source = document.createElement('source'); 1893 | source.src = newValue; 1894 | elt.appendChild(source); 1895 | self.elt.src = newValue; 1896 | self.modified = true; 1897 | } 1898 | }); 1899 | 1900 | // private _onended callback, set by the method: onended(callback) 1901 | self._onended = function() {}; 1902 | self.elt.onended = function() { 1903 | self._onended(self); 1904 | }; 1905 | }; 1906 | p5.MediaElement.prototype = Object.create(p5.Element.prototype); 1907 | 1908 | /** 1909 | * Play an HTML5 media element. 1910 | * 1911 | * @method play 1912 | * @return {Object|p5.Element} 1913 | * @example 1914 | *
1915 | * var ele; 1916 | * 1917 | * function setup() { 1918 | * //p5.MediaElement objects are usually created 1919 | * //by calling the createAudio(), createVideo(), 1920 | * //and createCapture() functions. 1921 | * 1922 | * //In this example we create 1923 | * //a new p5.MediaElement via createAudio(). 1924 | * ele = createAudio('assets/beat.mp3'); 1925 | * 1926 | * background(250); 1927 | * textAlign(CENTER); 1928 | * text('Click to Play!', width / 2, height / 2); 1929 | * } 1930 | * 1931 | * function mouseClicked() { 1932 | * //here we test if the mouse is over the 1933 | * //canvas element when it's clicked 1934 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { 1935 | * //Here we call the play() function on 1936 | * //the p5.MediaElement we created above. 1937 | * //This will start the audio sample. 1938 | * ele.play(); 1939 | * 1940 | * background(200); 1941 | * text('You clicked Play!', width / 2, height / 2); 1942 | * } 1943 | * } 1944 | *
1945 | */ 1946 | p5.MediaElement.prototype.play = function() { 1947 | if (this.elt.currentTime === this.elt.duration) { 1948 | this.elt.currentTime = 0; 1949 | } 1950 | 1951 | if (this.elt.readyState > 1) { 1952 | this.elt.play(); 1953 | } else { 1954 | // in Chrome, playback cannot resume after being stopped and must reload 1955 | this.elt.load(); 1956 | this.elt.play(); 1957 | } 1958 | return this; 1959 | }; 1960 | 1961 | /** 1962 | * Stops an HTML5 media element (sets current time to zero). 1963 | * 1964 | * @method stop 1965 | * @return {Object|p5.Element} 1966 | * @example 1967 | *
1968 | * //This example both starts 1969 | * //and stops a sound sample 1970 | * //when the user clicks the canvas 1971 | * 1972 | * //We will store the p5.MediaElement 1973 | * //object in here 1974 | * var ele; 1975 | * 1976 | * //while our audio is playing, 1977 | * //this will be set to true 1978 | * var sampleIsPlaying = false; 1979 | * 1980 | * function setup() { 1981 | * //Here we create a p5.MediaElement object 1982 | * //using the createAudio() function. 1983 | * ele = createAudio('assets/beat.mp3'); 1984 | * background(200); 1985 | * textAlign(CENTER); 1986 | * text('Click to play!', width / 2, height / 2); 1987 | * } 1988 | * 1989 | * function mouseClicked() { 1990 | * //here we test if the mouse is over the 1991 | * //canvas element when it's clicked 1992 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { 1993 | * background(200); 1994 | * 1995 | * if (sampleIsPlaying) { 1996 | * //if the sample is currently playing 1997 | * //calling the stop() function on 1998 | * //our p5.MediaElement will stop 1999 | * //it and reset its current 2000 | * //time to 0 (i.e. it will start 2001 | * //at the beginning the next time 2002 | * //you play it) 2003 | * ele.stop(); 2004 | * 2005 | * sampleIsPlaying = false; 2006 | * text('Click to play!', width / 2, height / 2); 2007 | * } else { 2008 | * //loop our sound element until we 2009 | * //call ele.stop() on it. 2010 | * ele.loop(); 2011 | * 2012 | * sampleIsPlaying = true; 2013 | * text('Click to stop!', width / 2, height / 2); 2014 | * } 2015 | * } 2016 | * } 2017 | *
2018 | */ 2019 | p5.MediaElement.prototype.stop = function() { 2020 | this.elt.pause(); 2021 | this.elt.currentTime = 0; 2022 | return this; 2023 | }; 2024 | 2025 | /** 2026 | * Pauses an HTML5 media element. 2027 | * 2028 | * @method pause 2029 | * @return {Object|p5.Element} 2030 | * @example 2031 | *
2032 | * //This example both starts 2033 | * //and pauses a sound sample 2034 | * //when the user clicks the canvas 2035 | * 2036 | * //We will store the p5.MediaElement 2037 | * //object in here 2038 | * var ele; 2039 | * 2040 | * //while our audio is playing, 2041 | * //this will be set to true 2042 | * var sampleIsPlaying = false; 2043 | * 2044 | * function setup() { 2045 | * //Here we create a p5.MediaElement object 2046 | * //using the createAudio() function. 2047 | * ele = createAudio('assets/lucky_dragons.mp3'); 2048 | * background(200); 2049 | * textAlign(CENTER); 2050 | * text('Click to play!', width / 2, height / 2); 2051 | * } 2052 | * 2053 | * function mouseClicked() { 2054 | * //here we test if the mouse is over the 2055 | * //canvas element when it's clicked 2056 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { 2057 | * background(200); 2058 | * 2059 | * if (sampleIsPlaying) { 2060 | * //Calling pause() on our 2061 | * //p5.MediaElement will stop it 2062 | * //playing, but when we call the 2063 | * //loop() or play() functions 2064 | * //the sample will start from 2065 | * //where we paused it. 2066 | * ele.pause(); 2067 | * 2068 | * sampleIsPlaying = false; 2069 | * text('Click to resume!', width / 2, height / 2); 2070 | * } else { 2071 | * //loop our sound element until we 2072 | * //call ele.pause() on it. 2073 | * ele.loop(); 2074 | * 2075 | * sampleIsPlaying = true; 2076 | * text('Click to pause!', width / 2, height / 2); 2077 | * } 2078 | * } 2079 | * } 2080 | *
2081 | */ 2082 | p5.MediaElement.prototype.pause = function() { 2083 | this.elt.pause(); 2084 | return this; 2085 | }; 2086 | 2087 | /** 2088 | * Set 'loop' to true for an HTML5 media element, and starts playing. 2089 | * 2090 | * @method loop 2091 | * @return {Object|p5.Element} 2092 | * @example 2093 | *
2094 | * //Clicking the canvas will loop 2095 | * //the audio sample until the user 2096 | * //clicks again to stop it 2097 | * 2098 | * //We will store the p5.MediaElement 2099 | * //object in here 2100 | * var ele; 2101 | * 2102 | * //while our audio is playing, 2103 | * //this will be set to true 2104 | * var sampleIsLooping = false; 2105 | * 2106 | * function setup() { 2107 | * //Here we create a p5.MediaElement object 2108 | * //using the createAudio() function. 2109 | * ele = createAudio('assets/lucky_dragons.mp3'); 2110 | * background(200); 2111 | * textAlign(CENTER); 2112 | * text('Click to loop!', width / 2, height / 2); 2113 | * } 2114 | * 2115 | * function mouseClicked() { 2116 | * //here we test if the mouse is over the 2117 | * //canvas element when it's clicked 2118 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { 2119 | * background(200); 2120 | * 2121 | * if (!sampleIsLooping) { 2122 | * //loop our sound element until we 2123 | * //call ele.stop() on it. 2124 | * ele.loop(); 2125 | * 2126 | * sampleIsLooping = true; 2127 | * text('Click to stop!', width / 2, height / 2); 2128 | * } else { 2129 | * ele.stop(); 2130 | * 2131 | * sampleIsLooping = false; 2132 | * text('Click to loop!', width / 2, height / 2); 2133 | * } 2134 | * } 2135 | * } 2136 | *
2137 | */ 2138 | p5.MediaElement.prototype.loop = function() { 2139 | this.elt.setAttribute('loop', true); 2140 | this.play(); 2141 | return this; 2142 | }; 2143 | /** 2144 | * Set 'loop' to false for an HTML5 media element. Element will stop 2145 | * when it reaches the end. 2146 | * 2147 | * @method noLoop 2148 | * @return {Object|p5.Element} 2149 | * @example 2150 | *
2151 | * //This example both starts 2152 | * //and stops loop of sound sample 2153 | * //when the user clicks the canvas 2154 | * 2155 | * //We will store the p5.MediaElement 2156 | * //object in here 2157 | * var ele; 2158 | * //while our audio is playing, 2159 | * //this will be set to true 2160 | * var sampleIsPlaying = false; 2161 | * 2162 | * function setup() { 2163 | * //Here we create a p5.MediaElement object 2164 | * //using the createAudio() function. 2165 | * ele = createAudio('assets/beat.mp3'); 2166 | * background(200); 2167 | * textAlign(CENTER); 2168 | * text('Click to play!', width / 2, height / 2); 2169 | * } 2170 | * 2171 | * function mouseClicked() { 2172 | * //here we test if the mouse is over the 2173 | * //canvas element when it's clicked 2174 | * if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) { 2175 | * background(200); 2176 | * 2177 | * if (sampleIsPlaying) { 2178 | * ele.noLoop(); 2179 | * text('No more Loops!', width / 2, height / 2); 2180 | * } else { 2181 | * ele.loop(); 2182 | * sampleIsPlaying = true; 2183 | * text('Click to stop looping!', width / 2, height / 2); 2184 | * } 2185 | * } 2186 | * } 2187 | *
2188 | * 2189 | */ 2190 | p5.MediaElement.prototype.noLoop = function() { 2191 | this.elt.setAttribute('loop', false); 2192 | return this; 2193 | }; 2194 | 2195 | /** 2196 | * Set HTML5 media element to autoplay or not. 2197 | * 2198 | * @method autoplay 2199 | * @param {Boolean} autoplay whether the element should autoplay 2200 | * @return {Object|p5.Element} 2201 | */ 2202 | p5.MediaElement.prototype.autoplay = function(val) { 2203 | this.elt.setAttribute('autoplay', val); 2204 | return this; 2205 | }; 2206 | 2207 | /** 2208 | * Sets volume for this HTML5 media element. If no argument is given, 2209 | * returns the current volume. 2210 | * 2211 | * @param {Number} [val] volume between 0.0 and 1.0 2212 | * @return {Number|p5.MediaElement} current volume or p5.MediaElement 2213 | * @method volume 2214 | * 2215 | * @example 2216 | *
2217 | * var ele; 2218 | * function setup() { 2219 | * // p5.MediaElement objects are usually created 2220 | * // by calling the createAudio(), createVideo(), 2221 | * // and createCapture() functions. 2222 | * // In this example we create 2223 | * // a new p5.MediaElement via createAudio(). 2224 | * ele = createAudio('assets/lucky_dragons.mp3'); 2225 | * background(250); 2226 | * textAlign(CENTER); 2227 | * text('Click to Play!', width / 2, height / 2); 2228 | * } 2229 | * function mouseClicked() { 2230 | * // Here we call the volume() function 2231 | * // on the sound element to set its volume 2232 | * // Volume must be between 0.0 and 1.0 2233 | * ele.volume(0.2); 2234 | * ele.play(); 2235 | * background(200); 2236 | * text('You clicked Play!', width / 2, height / 2); 2237 | * } 2238 | *
2239 | *
2240 | * var audio; 2241 | * var counter = 0; 2242 | * 2243 | * function loaded() { 2244 | * audio.play(); 2245 | * } 2246 | * 2247 | * function setup() { 2248 | * audio = createAudio('assets/lucky_dragons.mp3', loaded); 2249 | * textAlign(CENTER); 2250 | * } 2251 | * 2252 | * function draw() { 2253 | * if (counter === 0) { 2254 | * background(0, 255, 0); 2255 | * text('volume(0.9)', width / 2, height / 2); 2256 | * } else if (counter === 1) { 2257 | * background(255, 255, 0); 2258 | * text('volume(0.5)', width / 2, height / 2); 2259 | * } else if (counter === 2) { 2260 | * background(255, 0, 0); 2261 | * text('volume(0.1)', width / 2, height / 2); 2262 | * } 2263 | * } 2264 | * 2265 | * function mousePressed() { 2266 | * counter++; 2267 | * if (counter === 0) { 2268 | * audio.volume(0.9); 2269 | * } else if (counter === 1) { 2270 | * audio.volume(0.5); 2271 | * } else if (counter === 2) { 2272 | * audio.volume(0.1); 2273 | * } else { 2274 | * counter = 0; 2275 | * audio.volume(0.9); 2276 | * } 2277 | * } 2278 | * 2279 | *
2280 | */ 2281 | p5.MediaElement.prototype.volume = function(val) { 2282 | if (typeof val === 'undefined') { 2283 | return this.elt.volume; 2284 | } else { 2285 | this.elt.volume = val; 2286 | } 2287 | }; 2288 | 2289 | /** 2290 | * If no arguments are given, returns the current playback speed of the 2291 | * element. The speed parameter sets the speed where 2.0 will play the 2292 | * element twice as fast, 0.5 will play at half the speed, and -1 will play 2293 | * the element in normal speed in reverse.(Note that not all browsers support 2294 | * backward playback and even if they do, playback might not be smooth.) 2295 | * 2296 | * @method speed 2297 | * @param {Number} [speed] speed multiplier for element playback 2298 | * @return {Number|Object|p5.MediaElement} current playback speed or p5.MediaElement 2299 | * @example 2300 | *
2301 | * //Clicking the canvas will loop 2302 | * //the audio sample until the user 2303 | * //clicks again to stop it 2304 | * 2305 | * //We will store the p5.MediaElement 2306 | * //object in here 2307 | * var ele; 2308 | * var button; 2309 | * 2310 | * function setup() { 2311 | * createCanvas(710, 400); 2312 | * //Here we create a p5.MediaElement object 2313 | * //using the createAudio() function. 2314 | * ele = createAudio('assets/beat.mp3'); 2315 | * ele.loop(); 2316 | * background(200); 2317 | * 2318 | * button = createButton('2x speed'); 2319 | * button.position(100, 68); 2320 | * button.mousePressed(twice_speed); 2321 | * 2322 | * button = createButton('half speed'); 2323 | * button.position(200, 68); 2324 | * button.mousePressed(half_speed); 2325 | * 2326 | * button = createButton('reverse play'); 2327 | * button.position(300, 68); 2328 | * button.mousePressed(reverse_speed); 2329 | * 2330 | * button = createButton('STOP'); 2331 | * button.position(400, 68); 2332 | * button.mousePressed(stop_song); 2333 | * 2334 | * button = createButton('PLAY!'); 2335 | * button.position(500, 68); 2336 | * button.mousePressed(play_speed); 2337 | * } 2338 | * 2339 | * function twice_speed() { 2340 | * ele.speed(2); 2341 | * } 2342 | * 2343 | * function half_speed() { 2344 | * ele.speed(0.5); 2345 | * } 2346 | * 2347 | * function reverse_speed() { 2348 | * ele.speed(-1); 2349 | * } 2350 | * 2351 | * function stop_song() { 2352 | * ele.stop(); 2353 | * } 2354 | * 2355 | * function play_speed() { 2356 | * ele.play(); 2357 | * } 2358 | *
2359 | */ 2360 | p5.MediaElement.prototype.speed = function(val) { 2361 | if (typeof val === 'undefined') { 2362 | return this.elt.playbackRate; 2363 | } else { 2364 | this.elt.playbackRate = val; 2365 | } 2366 | }; 2367 | 2368 | /** 2369 | * If no arguments are given, returns the current time of the element. 2370 | * If an argument is given the current time of the element is set to it. 2371 | * 2372 | * @method time 2373 | * @param {Number} [time] time to jump to (in seconds) 2374 | * @return {Number|Object|p5.MediaElement} current time (in seconds) 2375 | * or p5.MediaElement 2376 | * @example 2377 | *
2378 | * var ele; 2379 | * var beginning = true; 2380 | * function setup() { 2381 | * //p5.MediaElement objects are usually created 2382 | * //by calling the createAudio(), createVideo(), 2383 | * //and createCapture() functions. 2384 | * 2385 | * //In this example we create 2386 | * //a new p5.MediaElement via createAudio(). 2387 | * ele = createAudio('assets/lucky_dragons.mp3'); 2388 | * background(250); 2389 | * textAlign(CENTER); 2390 | * text('start at beginning', width / 2, height / 2); 2391 | * } 2392 | * 2393 | * // this function fires with click anywhere 2394 | * function mousePressed() { 2395 | * if (beginning === true) { 2396 | * // here we start the sound at the beginning 2397 | * // time(0) is not necessary here 2398 | * // as this produces the same result as 2399 | * // play() 2400 | * ele.play().time(0); 2401 | * background(200); 2402 | * text('jump 2 sec in', width / 2, height / 2); 2403 | * beginning = false; 2404 | * } else { 2405 | * // here we jump 2 seconds into the sound 2406 | * ele.play().time(2); 2407 | * background(250); 2408 | * text('start at beginning', width / 2, height / 2); 2409 | * beginning = true; 2410 | * } 2411 | * } 2412 | *
2413 | */ 2414 | 2415 | p5.MediaElement.prototype.time = function(val) { 2416 | if (typeof val === 'undefined') { 2417 | return this.elt.currentTime; 2418 | } else { 2419 | this.elt.currentTime = val; 2420 | } 2421 | }; 2422 | 2423 | /** 2424 | * Returns the duration of the HTML5 media element. 2425 | * 2426 | * @method duration 2427 | * @return {Number} duration 2428 | * 2429 | * @example 2430 | *
2431 | * var ele; 2432 | * function setup() { 2433 | * //p5.MediaElement objects are usually created 2434 | * //by calling the createAudio(), createVideo(), 2435 | * //and createCapture() functions. 2436 | * //In this example we create 2437 | * //a new p5.MediaElement via createAudio(). 2438 | * ele = createAudio('assets/doorbell.mp3'); 2439 | * background(250); 2440 | * textAlign(CENTER); 2441 | * text('Click to know the duration!', 10, 25, 70, 80); 2442 | * } 2443 | * function mouseClicked() { 2444 | * ele.play(); 2445 | * background(200); 2446 | * //ele.duration dislpays the duration 2447 | * text(ele.duration() + ' seconds', width / 2, height / 2); 2448 | * } 2449 | *
2450 | */ 2451 | p5.MediaElement.prototype.duration = function() { 2452 | return this.elt.duration; 2453 | }; 2454 | p5.MediaElement.prototype.pixels = []; 2455 | p5.MediaElement.prototype.loadPixels = function() { 2456 | if (!this.canvas) { 2457 | this.canvas = document.createElement('canvas'); 2458 | this.drawingContext = this.canvas.getContext('2d'); 2459 | } 2460 | if (this.loadedmetadata) { 2461 | // wait for metadata for w/h 2462 | if (this.canvas.width !== this.elt.width) { 2463 | this.canvas.width = this.elt.width; 2464 | this.canvas.height = this.elt.height; 2465 | this.width = this.canvas.width; 2466 | this.height = this.canvas.height; 2467 | } 2468 | this.drawingContext.drawImage( 2469 | this.elt, 2470 | 0, 2471 | 0, 2472 | this.canvas.width, 2473 | this.canvas.height 2474 | ); 2475 | p5.Renderer2D.prototype.loadPixels.call(this); 2476 | } 2477 | this.setModified(true); 2478 | return this; 2479 | }; 2480 | p5.MediaElement.prototype.updatePixels = function(x, y, w, h) { 2481 | if (this.loadedmetadata) { 2482 | // wait for metadata 2483 | p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h); 2484 | } 2485 | this.setModified(true); 2486 | return this; 2487 | }; 2488 | p5.MediaElement.prototype.get = function(x, y, w, h) { 2489 | if (this.loadedmetadata) { 2490 | // wait for metadata 2491 | return p5.Renderer2D.prototype.get.call(this, x, y, w, h); 2492 | } else if (typeof x === 'undefined') { 2493 | return new p5.Image(1, 1); 2494 | } else if (w > 1) { 2495 | return new p5.Image(x, y, w, h); 2496 | } else { 2497 | return [0, 0, 0, 255]; 2498 | } 2499 | }; 2500 | p5.MediaElement.prototype.set = function(x, y, imgOrCol) { 2501 | if (this.loadedmetadata) { 2502 | // wait for metadata 2503 | p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol); 2504 | this.setModified(true); 2505 | } 2506 | }; 2507 | p5.MediaElement.prototype.copy = function() { 2508 | p5.Renderer2D.prototype.copy.apply(this, arguments); 2509 | }; 2510 | p5.MediaElement.prototype.mask = function() { 2511 | this.loadPixels(); 2512 | this.setModified(true); 2513 | p5.Image.prototype.mask.apply(this, arguments); 2514 | }; 2515 | /** 2516 | * helper method for web GL mode to figure out if the element 2517 | * has been modified and might need to be re-uploaded to texture 2518 | * memory between frames. 2519 | * @method isModified 2520 | * @private 2521 | * @return {boolean} a boolean indicating whether or not the 2522 | * image has been updated or modified since last texture upload. 2523 | */ 2524 | p5.MediaElement.prototype.isModified = function() { 2525 | return this._modified; 2526 | }; 2527 | /** 2528 | * helper method for web GL mode to indicate that an element has been 2529 | * changed or unchanged since last upload. gl texture upload will 2530 | * set this value to false after uploading the texture; or might set 2531 | * it to true if metadata has become available but there is no actual 2532 | * texture data available yet.. 2533 | * @method setModified 2534 | * @param {boolean} val sets whether or not the element has been 2535 | * modified. 2536 | * @private 2537 | */ 2538 | p5.MediaElement.prototype.setModified = function(value) { 2539 | this._modified = value; 2540 | }; 2541 | /** 2542 | * Schedule an event to be called when the audio or video 2543 | * element reaches the end. If the element is looping, 2544 | * this will not be called. The element is passed in 2545 | * as the argument to the onended callback. 2546 | * 2547 | * @method onended 2548 | * @param {Function} callback function to call when the 2549 | * soundfile has ended. The 2550 | * media element will be passed 2551 | * in as the argument to the 2552 | * callback. 2553 | * @return {Object|p5.MediaElement} 2554 | * @example 2555 | *
2556 | * function setup() { 2557 | * var audioEl = createAudio('assets/beat.mp3'); 2558 | * audioEl.showControls(); 2559 | * audioEl.onended(sayDone); 2560 | * } 2561 | * 2562 | * function sayDone(elt) { 2563 | * alert('done playing ' + elt.src); 2564 | * } 2565 | *
2566 | */ 2567 | p5.MediaElement.prototype.onended = function(callback) { 2568 | this._onended = callback; 2569 | return this; 2570 | }; 2571 | 2572 | /*** CONNECT TO WEB AUDIO API / p5.sound.js ***/ 2573 | 2574 | /** 2575 | * Send the audio output of this element to a specified audioNode or 2576 | * p5.sound object. If no element is provided, connects to p5's master 2577 | * output. That connection is established when this method is first called. 2578 | * All connections are removed by the .disconnect() method. 2579 | * 2580 | * This method is meant to be used with the p5.sound.js addon library. 2581 | * 2582 | * @method connect 2583 | * @param {AudioNode|Object} audioNode AudioNode from the Web Audio API, 2584 | * or an object from the p5.sound library 2585 | */ 2586 | p5.MediaElement.prototype.connect = function(obj) { 2587 | var audioContext, masterOutput; 2588 | 2589 | // if p5.sound exists, same audio context 2590 | if (typeof p5.prototype.getAudioContext === 'function') { 2591 | audioContext = p5.prototype.getAudioContext(); 2592 | masterOutput = p5.soundOut.input; 2593 | } else { 2594 | try { 2595 | audioContext = obj.context; 2596 | masterOutput = audioContext.destination; 2597 | } catch (e) { 2598 | throw 'connect() is meant to be used with Web Audio API or p5.sound.js'; 2599 | } 2600 | } 2601 | 2602 | // create a Web Audio MediaElementAudioSourceNode if none already exists 2603 | if (!this.audioSourceNode) { 2604 | this.audioSourceNode = audioContext.createMediaElementSource(this.elt); 2605 | 2606 | // connect to master output when this method is first called 2607 | this.audioSourceNode.connect(masterOutput); 2608 | } 2609 | 2610 | // connect to object if provided 2611 | if (obj) { 2612 | if (obj.input) { 2613 | this.audioSourceNode.connect(obj.input); 2614 | } else { 2615 | this.audioSourceNode.connect(obj); 2616 | } 2617 | } else { 2618 | // otherwise connect to master output of p5.sound / AudioContext 2619 | this.audioSourceNode.connect(masterOutput); 2620 | } 2621 | }; 2622 | 2623 | /** 2624 | * Disconnect all Web Audio routing, including to master output. 2625 | * This is useful if you want to re-route the output through 2626 | * audio effects, for example. 2627 | * 2628 | * @method disconnect 2629 | */ 2630 | p5.MediaElement.prototype.disconnect = function() { 2631 | if (this.audioSourceNode) { 2632 | this.audioSourceNode.disconnect(); 2633 | } else { 2634 | throw 'nothing to disconnect'; 2635 | } 2636 | }; 2637 | 2638 | /*** SHOW / HIDE CONTROLS ***/ 2639 | 2640 | /** 2641 | * Show the default MediaElement controls, as determined by the web browser. 2642 | * 2643 | * @method showControls 2644 | * @example 2645 | *
2646 | * var ele; 2647 | * function setup() { 2648 | * //p5.MediaElement objects are usually created 2649 | * //by calling the createAudio(), createVideo(), 2650 | * //and createCapture() functions. 2651 | * //In this example we create 2652 | * //a new p5.MediaElement via createAudio() 2653 | * ele = createAudio('assets/lucky_dragons.mp3'); 2654 | * background(200); 2655 | * textAlign(CENTER); 2656 | * text('Click to Show Controls!', 10, 25, 70, 80); 2657 | * } 2658 | * function mousePressed() { 2659 | * ele.showControls(); 2660 | * background(200); 2661 | * text('Controls Shown', width / 2, height / 2); 2662 | * } 2663 | *
2664 | */ 2665 | p5.MediaElement.prototype.showControls = function() { 2666 | // must set style for the element to show on the page 2667 | this.elt.style['text-align'] = 'inherit'; 2668 | this.elt.controls = true; 2669 | }; 2670 | 2671 | /** 2672 | * Hide the default mediaElement controls. 2673 | * @method hideControls 2674 | * @example 2675 | *
2676 | * var ele; 2677 | * function setup() { 2678 | * //p5.MediaElement objects are usually created 2679 | * //by calling the createAudio(), createVideo(), 2680 | * //and createCapture() functions. 2681 | * //In this example we create 2682 | * //a new p5.MediaElement via createAudio() 2683 | * ele = createAudio('assets/lucky_dragons.mp3'); 2684 | * ele.showControls(); 2685 | * background(200); 2686 | * textAlign(CENTER); 2687 | * text('Click to hide Controls!', 10, 25, 70, 80); 2688 | * } 2689 | * function mousePressed() { 2690 | * ele.hideControls(); 2691 | * background(200); 2692 | * text('Controls hidden', width / 2, height / 2); 2693 | * } 2694 | *
2695 | */ 2696 | p5.MediaElement.prototype.hideControls = function() { 2697 | this.elt.controls = false; 2698 | }; 2699 | 2700 | /*** SCHEDULE EVENTS ***/ 2701 | 2702 | // Cue inspired by JavaScript setTimeout, and the 2703 | // Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org 2704 | var Cue = function(callback, time, id, val) { 2705 | this.callback = callback; 2706 | this.time = time; 2707 | this.id = id; 2708 | this.val = val; 2709 | }; 2710 | 2711 | /** 2712 | * Schedule events to trigger every time a MediaElement 2713 | * (audio/video) reaches a playback cue point. 2714 | * 2715 | * Accepts a callback function, a time (in seconds) at which to trigger 2716 | * the callback, and an optional parameter for the callback. 2717 | * 2718 | * Time will be passed as the first parameter to the callback function, 2719 | * and param will be the second parameter. 2720 | * 2721 | * 2722 | * @method addCue 2723 | * @param {Number} time Time in seconds, relative to this media 2724 | * element's playback. For example, to trigger 2725 | * an event every time playback reaches two 2726 | * seconds, pass in the number 2. This will be 2727 | * passed as the first parameter to 2728 | * the callback function. 2729 | * @param {Function} callback Name of a function that will be 2730 | * called at the given time. The callback will 2731 | * receive time and (optionally) param as its 2732 | * two parameters. 2733 | * @param {Object} [value] An object to be passed as the 2734 | * second parameter to the 2735 | * callback function. 2736 | * @return {Number} id ID of this cue, 2737 | * useful for removeCue(id) 2738 | * @example 2739 | *
2740 | * function setup() { 2741 | * background(255, 255, 255); 2742 | * 2743 | * var audioEl = createAudio('assets/beat.mp3'); 2744 | * audioEl.showControls(); 2745 | * 2746 | * // schedule three calls to changeBackground 2747 | * audioEl.addCue(0.5, changeBackground, color(255, 0, 0)); 2748 | * audioEl.addCue(1.0, changeBackground, color(0, 255, 0)); 2749 | * audioEl.addCue(2.5, changeBackground, color(0, 0, 255)); 2750 | * audioEl.addCue(3.0, changeBackground, color(0, 255, 255)); 2751 | * audioEl.addCue(4.2, changeBackground, color(255, 255, 0)); 2752 | * audioEl.addCue(5.0, changeBackground, color(255, 255, 0)); 2753 | * } 2754 | * 2755 | * function changeBackground(val) { 2756 | * background(val); 2757 | * } 2758 | *
2759 | */ 2760 | p5.MediaElement.prototype.addCue = function(time, callback, val) { 2761 | var id = this._cueIDCounter++; 2762 | 2763 | var cue = new Cue(callback, time, id, val); 2764 | this._cues.push(cue); 2765 | 2766 | if (!this.elt.ontimeupdate) { 2767 | this.elt.ontimeupdate = this._onTimeUpdate.bind(this); 2768 | } 2769 | 2770 | return id; 2771 | }; 2772 | 2773 | /** 2774 | * Remove a callback based on its ID. The ID is returned by the 2775 | * addCue method. 2776 | * @method removeCue 2777 | * @param {Number} id ID of the cue, as returned by addCue 2778 | * @example 2779 | *
2780 | * var audioEl, id1, id2; 2781 | * function setup() { 2782 | * background(255, 255, 255); 2783 | * audioEl = createAudio('assets/beat.mp3'); 2784 | * audioEl.showControls(); 2785 | * // schedule five calls to changeBackground 2786 | * id1 = audioEl.addCue(0.5, changeBackground, color(255, 0, 0)); 2787 | * audioEl.addCue(1.0, changeBackground, color(0, 255, 0)); 2788 | * audioEl.addCue(2.5, changeBackground, color(0, 0, 255)); 2789 | * audioEl.addCue(3.0, changeBackground, color(0, 255, 255)); 2790 | * id2 = audioEl.addCue(4.2, changeBackground, color(255, 255, 0)); 2791 | * text('Click to remove first and last Cue!', 10, 25, 70, 80); 2792 | * } 2793 | * function mousePressed() { 2794 | * audioEl.removeCue(id1); 2795 | * audioEl.removeCue(id2); 2796 | * } 2797 | * function changeBackground(val) { 2798 | * background(val); 2799 | * } 2800 | *
2801 | */ 2802 | p5.MediaElement.prototype.removeCue = function(id) { 2803 | for (var i = 0; i < this._cues.length; i++) { 2804 | if (this._cues[i].id === id) { 2805 | console.log(id); 2806 | this._cues.splice(i, 1); 2807 | } 2808 | } 2809 | 2810 | if (this._cues.length === 0) { 2811 | this.elt.ontimeupdate = null; 2812 | } 2813 | }; 2814 | 2815 | /** 2816 | * Remove all of the callbacks that had originally been scheduled 2817 | * via the addCue method. 2818 | * @method clearCues 2819 | * @param {Number} id ID of the cue, as returned by addCue 2820 | * @example 2821 | *
2822 | * var audioEl; 2823 | * function setup() { 2824 | * background(255, 255, 255); 2825 | * audioEl = createAudio('assets/beat.mp3'); 2826 | * //Show the default MediaElement controls, as determined by the web browser 2827 | * audioEl.showControls(); 2828 | * // schedule calls to changeBackground 2829 | * background(200); 2830 | * text('Click to change Cue!', 10, 25, 70, 80); 2831 | * audioEl.addCue(0.5, changeBackground, color(255, 0, 0)); 2832 | * audioEl.addCue(1.0, changeBackground, color(0, 255, 0)); 2833 | * audioEl.addCue(2.5, changeBackground, color(0, 0, 255)); 2834 | * audioEl.addCue(3.0, changeBackground, color(0, 255, 255)); 2835 | * audioEl.addCue(4.2, changeBackground, color(255, 255, 0)); 2836 | * } 2837 | * function mousePressed() { 2838 | * // here we clear the scheduled callbacks 2839 | * audioEl.clearCues(); 2840 | * // then we add some more callbacks 2841 | * audioEl.addCue(1, changeBackground, color(2, 2, 2)); 2842 | * audioEl.addCue(3, changeBackground, color(255, 255, 0)); 2843 | * } 2844 | * function changeBackground(val) { 2845 | * background(val); 2846 | * } 2847 | *
2848 | */ 2849 | p5.MediaElement.prototype.clearCues = function() { 2850 | this._cues = []; 2851 | this.elt.ontimeupdate = null; 2852 | }; 2853 | 2854 | // private method that checks for cues to be fired if events 2855 | // have been scheduled using addCue(callback, time). 2856 | p5.MediaElement.prototype._onTimeUpdate = function() { 2857 | var playbackTime = this.time(); 2858 | 2859 | for (var i = 0; i < this._cues.length; i++) { 2860 | var callbackTime = this._cues[i].time; 2861 | var val = this._cues[i].val; 2862 | 2863 | if (this._prevTime < callbackTime && callbackTime <= playbackTime) { 2864 | // pass the scheduled callbackTime as parameter to the callback 2865 | this._cues[i].callback(val); 2866 | } 2867 | } 2868 | 2869 | this._prevTime = playbackTime; 2870 | }; 2871 | 2872 | // ============================================================================= 2873 | // p5.File 2874 | // ============================================================================= 2875 | 2876 | /** 2877 | * Base class for a file 2878 | * Using this for createFileInput 2879 | * 2880 | * @class p5.File 2881 | * @constructor 2882 | * @param {File} file File that is wrapped 2883 | */ 2884 | p5.File = function(file, pInst) { 2885 | /** 2886 | * Underlying File object. All normal File methods can be called on this. 2887 | * 2888 | * @property file 2889 | */ 2890 | this.file = file; 2891 | 2892 | this._pInst = pInst; 2893 | 2894 | // Splitting out the file type into two components 2895 | // This makes determining if image or text etc simpler 2896 | var typeList = file.type.split('/'); 2897 | /** 2898 | * File type (image, text, etc.) 2899 | * 2900 | * @property type 2901 | */ 2902 | this.type = typeList[0]; 2903 | /** 2904 | * File subtype (usually the file extension jpg, png, xml, etc.) 2905 | * 2906 | * @property subtype 2907 | */ 2908 | this.subtype = typeList[1]; 2909 | /** 2910 | * File name 2911 | * 2912 | * @property name 2913 | */ 2914 | this.name = file.name; 2915 | /** 2916 | * File size 2917 | * 2918 | * @property size 2919 | */ 2920 | this.size = file.size; 2921 | 2922 | /** 2923 | * URL string containing image data. 2924 | * 2925 | * @property data 2926 | */ 2927 | this.data = undefined; 2928 | }; 2929 | }); 2930 | --------------------------------------------------------------------------------