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