p5
Version:
[](https://www.npmjs.com/package/p5)
1,821 lines (1,774 loc) • 68.9 kB
JavaScript
import { File } from './p5.File.js';
import { C as Color } from '../creating_reading-Cr8L2Jnm.js';
import { w as AUTO } from '../constants-BRcElHU3.js';
import '../io/p5.XML.js';
import 'colorjs.io/fn';
import '../color/color_spaces/hsb.js';
/**
* @module DOM
* @submodule DOM
*/
class Element {
width;
height;
elt;
constructor(elt, pInst) {
this.elt = elt;
this._pInst = this._pixelsState = pInst;
this._events = {};
this.width = this.elt.offsetWidth;
this.height = this.elt.offsetHeight;
}
/**
* Removes the element, stops all audio/video streams, and removes all
* callback functions.
*
* @example
* <div>
* <code>
* let p;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a paragraph element.
* p = createP('p5*js');
* p.position(10, 10);
*
* describe('The text "p5*js" written at the center of a gray square. ');
* }
*
* // Remove the paragraph when the user double-clicks.
* function doubleClicked() {
* p.remove();
* }
* </code>
* </div>
*/
remove() {
// stop all audios/videos and detach all devices like microphone/camera etc
// used as input/output for audios/videos.
// if (this instanceof p5.MediaElement) {
if(this.stop){
this.stop();
const sources = this.elt.srcObject;
if (sources !== null) {
const tracks = sources.getTracks();
tracks.forEach(track => {
track.stop();
});
}
}
// delete the reference in this._pInst._elements
const index = this._pInst._elements.indexOf(this);
if (index !== -1) {
this._pInst._elements.splice(index, 1);
}
// deregister events
for (let ev in this._events) {
this.elt.removeEventListener(ev, this._events[ev]);
}
if (this.elt && this.elt.parentNode) {
this.elt.parentNode.removeChild(this.elt);
}
}
/**
* Attaches the element to a parent element.
*
* For example, a `<div></div>` element may be used as a box to
* hold two pieces of text, a header and a paragraph. The
* `<div></div>` is the parent element of both the header and
* paragraph.
*
* The parameter `parent` can have one of three types. `parent` can be a
* string with the parent element's ID, as in
* `myElement.parent('container')`. It can also be another
* <a href="#/p5.Element">p5.Element</a> object, as in
* `myElement.parent(myDiv)`. Finally, `parent` can be an `HTMLElement`
* object, as in `myElement.parent(anotherElement)`.
*
* Calling `myElement.parent()` without an argument returns the element's
* parent.
*
* @param {String|p5.Element|Object} parent ID, <a href="#/p5.Element">p5.Element</a>,
* or HTMLElement of desired parent element.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* background(200);
*
* // Create a div element.
* let div = createDiv();
*
* // Place the div in the top-left corner.
* div.position(10, 20);
*
* // Set its width and height.
* div.size(80, 60);
*
* // Set its background color to white
* div.style('background-color', 'white');
*
* // Align any text to the center.
* div.style('text-align', 'center');
*
* // Set its ID to "container".
* div.id('container');
*
* // Create a paragraph element.
* let p = createP('p5*js');
*
* // Make the div its parent
* // using its ID "container".
* p.parent('container');
*
* describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* background(200);
*
* // Create rectangular div element.
* let div = createDiv();
*
* // Place the div in the top-left corner.
* div.position(10, 20);
*
* // Set its width and height.
* div.size(80, 60);
*
* // Set its background color and align
* // any text to the center.
* div.style('background-color', 'white');
* div.style('text-align', 'center');
*
* // Create a paragraph element.
* let p = createP('p5*js');
*
* // Make the div its parent.
* p.parent(div);
*
* describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* background(200);
*
* // Create rectangular div element.
* let div = createDiv();
*
* // Place the div in the top-left corner.
* div.position(10, 20);
*
* // Set its width and height.
* div.size(80, 60);
*
* // Set its background color and align
* // any text to the center.
* div.style('background-color', 'white');
* div.style('text-align', 'center');
*
* // Create a paragraph element.
* let p = createP('p5*js');
*
* // Make the div its parent
* // using the underlying
* // HTMLElement.
* p.parent(div.elt);
*
* describe('The text "p5*js" written in black at the center of a white rectangle. The rectangle is inside a gray square.');
* }
* </code>
* </div>
*/
/**
* @return {p5.Element}
*/
parent(p) {
if (typeof p === 'undefined') {
return this.elt.parentNode;
}
if (typeof p === 'string') {
if (p[0] === '#') {
p = p.substring(1);
}
p = document.getElementById(p);
} else if (p instanceof Element) {
p = p.elt;
}
p.appendChild(this.elt);
return this;
}
/**
* Attaches the element as a child of another element.
*
* `myElement.child()` accepts either a string ID, DOM node, or
* <a href="#/p5.Element">p5.Element</a>. For example,
* `myElement.child(otherElement)`. If no argument is provided, an array of
* children DOM nodes is returned.
*
* @returns {Node[]} an array of child nodes.
*
* @example
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create the div elements.
* let div0 = createDiv('Parent');
* let div1 = createDiv('Child');
*
* // Make div1 the child of div0
* // using the p5.Element.
* div0.child(div1);
*
* describe('A gray square with the words "Parent" and "Child" written beneath it.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create the div elements.
* let div0 = createDiv('Parent');
* let div1 = createDiv('Child');
*
* // Give div1 an ID.
* div1.id('apples');
*
* // Make div1 the child of div0
* // using its ID.
* div0.child('apples');
*
* describe('A gray square with the words "Parent" and "Child" written beneath it.');
* }
* </code>
* </div>
*
* <div class='norender notest'>
* <code>
* // This example assumes there is a div already on the page
* // with id "myChildDiv".
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create the div elements.
* let div0 = createDiv('Parent');
*
* // Select the child element by its ID.
* let elt = document.getElementById('myChildDiv');
*
* // Make div1 the child of div0
* // using its HTMLElement object.
* div0.child(elt);
*
* describe('A gray square with the words "Parent" and "Child" written beneath it.');
* }
* </code>
* </div>
*/
/**
* @param {String|p5.Element} [child] the ID, DOM node, or <a href="#/p5.Element">p5.Element</a>
* to add to the current element
* @chainable
*/
child(childNode) {
if (typeof childNode === 'undefined') {
return this.elt.childNodes;
}
if (typeof childNode === 'string') {
if (childNode[0] === '#') {
childNode = childNode.substring(1);
}
childNode = document.getElementById(childNode);
} else if (childNode instanceof Element) {
childNode = childNode.elt;
}
if (childNode instanceof HTMLElement) {
this.elt.appendChild(childNode);
}
return this;
}
/**
* Sets the inner HTML of the element, replacing any existing HTML.
*
* The second parameter, `append`, is optional. If `true` is passed, as in
* `myElement.html('hi', true)`, the HTML is appended instead of replacing
* existing HTML.
*
* If no arguments are passed, as in `myElement.html()`, the element's inner
* HTML is returned.
*
* @for p5.Element
* @returns {String} the inner HTML of the element
*
* @example
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* // Create the div element and set its size.
* let div = createDiv('');
* div.size(100, 100);
*
* // Set the inner HTML to "hi".
* div.html('hi');
*
* describe('A gray square with the word "hi" written beneath it.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create the div element and set its size.
* let div = createDiv('Hello ');
* div.size(100, 100);
*
* // Append "World" to the div's HTML.
* div.html('World', true);
*
* describe('A gray square with the text "Hello World" written beneath it.');
* }
* </code>
* </div>
*
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create the div element.
* let div = createDiv('Hello');
*
* // Prints "Hello" to the console.
* print(div.html());
*
* describe('A gray square with the word "Hello!" written beneath it.');
* }
* </code>
* </div>
*/
/**
* @param {String} [html] the HTML to be placed inside the element
* @param {Boolean} [append] whether to append HTML to existing
* @chainable
*/
html(...args) {
if (args.length === 0) {
return this.elt.innerHTML;
} else if (args[1]) {
this.elt.insertAdjacentHTML('beforeend', args[0]);
return this;
} else {
this.elt.innerHTML = args[0];
return this;
}
}
/**
* Sets the element's ID using a given string.
*
* Calling `myElement.id()` without an argument returns its ID as a string.
*
* @param {String} id ID of the element.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Set the canvas' ID
* // to "mycanvas".
* cnv.id('mycanvas');
*
* // Get the canvas' ID.
* let id = cnv.id();
* text(id, 24, 54);
*
* describe('The text "mycanvas" written in black on a gray background.');
* }
* </code>
* </div>
*/
/**
* @return {String} ID of the element.
*/
id(id) {
if (typeof id === 'undefined') {
return this.elt.id;
}
this.elt.id = id;
this.width = this.elt.offsetWidth;
this.height = this.elt.offsetHeight;
return this;
}
/**
* Adds a
* <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class" target="_blank">class attribute</a>
* to the element using a given string.
*
* Calling `myElement.class()` without an argument returns a string with its current classes.
*
* @param {String} class class to add.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Add the class "small" to the
* // canvas element.
* cnv.class('small');
*
* // Get the canvas element's class
* // and display it.
* let c = cnv.class();
* text(c, 35, 54);
*
* describe('The word "small" written in black on a gray canvas.');
*
* }
* </code>
* </div>
*/
/**
* @return {String} element's classes, if any.
*/
class(c) {
if (typeof c === 'undefined') {
return this.elt.className;
}
this.elt.className = c;
return this;
}
/**
*
* Adds a class to the element.
*
* @for p5.Element
* @param {String} class name of class to add.
* @chainable
*
* @example
* <div class='norender'>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a div element.
* let div = createDiv('div');
*
* // Add a class to the div.
* div.addClass('myClass');
*
* describe('A gray square.');
* }
* </code>
* </div>
*/
addClass(c) {
if (this.elt.className) {
if (!this.hasClass(c)) {
this.elt.className = this.elt.className + ' ' + c;
}
} else {
this.elt.className = c;
}
return this;
}
/**
* Removes a class from the element.
*
* @param {String} class name of class to remove.
* @chainable
*
* @example
* <div class='norender'>
* <code>
* // In this example, a class is set when the div is created
* // and removed when mouse is pressed. This could link up
* // with a CSS style rule to toggle style properties.
*
* let div;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a div element.
* div = createDiv('div');
*
* // Add a class to the div.
* div.addClass('myClass');
*
* describe('A gray square.');
* }
*
* // Remove 'myClass' from the div when the user presses the mouse.
* function mousePressed() {
* div.removeClass('myClass');
* }
* </code>
* </div>
*/
removeClass(c) {
// Note: Removing a class that does not exist does NOT throw an error in classList.remove method
this.elt.classList.remove(c);
return this;
}
/**
* Checks if a class is already applied to element.
*
* @returns {boolean} a boolean value if element has specified class.
* @param c {String} name of class to check.
*
* @example
* <div class='norender'>
* <code>
* let div;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a div element.
* div = createDiv('div');
*
* // Add the class 'show' to the div.
* div.addClass('show');
*
* describe('A gray square.');
* }
*
* // Toggle the class 'show' when the mouse is pressed.
* function mousePressed() {
* if (div.hasClass('show')) {
* div.addClass('show');
* } else {
* div.removeClass('show');
* }
* }
* </code>
* </div>
*/
hasClass(c) {
return this.elt.classList.contains(c);
}
/**
* Toggles whether a class is applied to the element.
*
* @param c {String} class name to toggle.
* @chainable
*
* @example
* <div class='norender'>
* <code>
* let div;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a div element.
* div = createDiv('div');
*
* // Add the 'show' class to the div.
* div.addClass('show');
*
* describe('A gray square.');
* }
*
* // Toggle the 'show' class when the mouse is pressed.
* function mousePressed() {
* div.toggleClass('show');
* }
* </code>
* </div>
*/
toggleClass(c) {
// classList also has a toggle() method, but we cannot use that yet as support is unclear.
// See https://github.com/processing/p5.js/issues/3631
// this.elt.classList.toggle(c);
if (this.elt.classList.contains(c)) {
this.elt.classList.remove(c);
} else {
this.elt.classList.add(c);
}
return this;
}
/**
* Centers the element either vertically, horizontally, or both.
*
* `center()` will center the element relative to its parent or according to
* the page's body if the element has no parent.
*
* If no argument is passed, as in `myElement.center()` the element is aligned
* both vertically and horizontally.
*
* @param {String} [align] passing 'vertical', 'horizontal' aligns element accordingly
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create the div element and style it.
* let div = createDiv('');
* div.size(10, 10);
* div.style('background-color', 'orange');
*
* // Center the div relative to the page's body.
* div.center();
*
* describe('A gray square and an orange rectangle. The rectangle is at the center of the page.');
* }
* </code>
* </div>
*/
center(align) {
const style = this.elt.style.display;
const hidden = this.elt.style.display === 'none';
const parentHidden = this.parent().style.display === 'none';
const pos = { x: this.elt.offsetLeft, y: this.elt.offsetTop };
if (hidden) this.show();
if (parentHidden) this.parent().show();
this.elt.style.display = 'block';
this.position(0, 0);
const wOffset = Math.abs(this.parent().offsetWidth - this.elt.offsetWidth);
const hOffset = Math.abs(this.parent().offsetHeight - this.elt.offsetHeight);
if (align === 'both' || align === undefined) {
this.position(
wOffset / 2 + this.parent().offsetLeft,
hOffset / 2 + this.parent().offsetTop
);
} else if (align === 'horizontal') {
this.position(wOffset / 2 + this.parent().offsetLeft, pos.y);
} else if (align === 'vertical') {
this.position(pos.x, hOffset / 2 + this.parent().offsetTop);
}
this.style('display', style);
if (hidden) this.hide();
if (parentHidden) this.parent().hide();
return this;
}
/**
* Sets the element's position.
*
* The first two parameters, `x` and `y`, set the element's position relative
* to the top-left corner of the web page.
*
* The third parameter, `positionType`, is optional. It sets the element's
* <a target="_blank"
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/position">positioning scheme</a>.
* `positionType` is a string that can be either `'static'`, `'fixed'`,
* `'relative'`, `'sticky'`, `'initial'`, or `'inherit'`.
*
* If no arguments passed, as in `myElement.position()`, the method returns
* the element's position in an object, as in `{ x: 0, y: 0 }`.
*
* @returns {Object} object of form `{ x: 0, y: 0 }` containing the element's position.
*
* @example
* <div>
* <code class='norender'>
* function setup() {
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Positions the canvas 50px to the right and 100px
* // below the top-left corner of the window.
* cnv.position(50, 100);
*
* describe('A gray square that is 50 pixels to the right and 100 pixels down from the top-left corner of the web page.');
* }
* </code>
* </div>
*
* <div>
* <code class='norender'>
* function setup() {
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Positions the canvas at the top-left corner
* // of the window with a 'fixed' position type.
* cnv.position(0, 0, 'fixed');
*
* describe('A gray square in the top-left corner of the web page.');
* }
* </code>
* </div>
*/
/**
* @param {Number} [x] x-position relative to top-left of window (optional)
* @param {Number} [y] y-position relative to top-left of window (optional)
* @param {String} [positionType] it can be static, fixed, relative, sticky, initial or inherit (optional)
* @chainable
*/
position(...args) {
if (args.length === 0) {
return { x: this.elt.offsetLeft, y: this.elt.offsetTop };
} else {
let positionType = 'absolute';
if (
args[2] === 'static' ||
args[2] === 'fixed' ||
args[2] === 'relative' ||
args[2] === 'sticky' ||
args[2] === 'initial' ||
args[2] === 'inherit'
) {
positionType = args[2];
}
this.elt.style.position = positionType;
this.elt.style.left = args[0] + 'px';
this.elt.style.top = args[1] + 'px';
this.x = args[0];
this.y = args[1];
return this;
}
}
/**
* Shows the current element.
*
* @chainable
*
* @example
* <div>
* <code>
* let p;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a paragraph element and hide it.
* p = createP('p5*js');
* p.position(10, 10);
* p.hide();
*
* describe('A gray square. The text "p5*js" appears when the user double-clicks the square.');
* }
*
* // Show the paragraph when the user double-clicks.
* function doubleClicked() {
* p.show();
* }
* </code>
* </div>
*/
show() {
this.elt.style.display = 'block';
return this;
}
/**
* Hides the current element.
*
* @chainable
*
* @example
* let p;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a paragraph element.
* p = createP('p5*js');
* p.position(10, 10);
*
* describe('The text "p5*js" at the center of a gray square. The text disappears when the user double-clicks the square.');
* }
*
* // Hide the paragraph when the user double-clicks.
* function doubleClicked() {
* p.hide();
* }
* </code>
* </div>
*/
hide() {
this.elt.style.display = 'none';
return this;
}
/**
* Sets the element's width and height.
*
* Calling `myElement.size()` without an argument returns the element's size
* as an object with the properties `width` and `height`. For example,
* `{ width: 20, height: 10 }`.
*
* The first parameter, `width`, is optional. It's a number used to set the
* element's width. Calling `myElement.size(10)`
*
* The second parameter, 'height`, is also optional. It's a
* number used to set the element's height. For example, calling
* `myElement.size(20, 10)` sets the element's width to 20 pixels and height
* to 10 pixels.
*
* The constant `AUTO` can be used to adjust one dimension at a time while
* maintaining the aspect ratio, which is `width / height`. For example,
* consider an element that's 200 pixels wide and 100 pixels tall. Calling
* `myElement.size(20, AUTO)` sets the width to 20 pixels and height to 10
* pixels.
*
* Note: In the case of elements that need to load data, such as images, wait
* to call `myElement.size()` until after the data loads.
*
* @return {Object} width and height of the element in an object.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a pink div element and place it at the top-left corner.
* let div = createDiv();
* div.position(10, 10);
* div.style('background-color', 'deeppink');
*
* // Set the div's width to 80 pixels and height to 20 pixels.
* div.size(80, 20);
*
* describe('A gray square with a pink rectangle near its top.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a pink div element and place it at the top-left corner.
* let div = createDiv();
* div.position(10, 10);
* div.style('background-color', 'deeppink');
*
* // Set the div's width to 80 pixels and height to 40 pixels.
* div.size(80, 40);
*
* // Get the div's size as an object.
* let s = div.size();
*
* // Display the div's dimensions.
* div.html(`${s.width} x ${s.height}`);
*
* describe('A gray square with a pink rectangle near its top. The text "80 x 40" is written within the rectangle.');
* }
* </code>
* </div>
*
* <div>
* <code>
* let img1;
* let img2;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Load an image of an astronaut on the moon
* // and place it at the top-left of the canvas.
* img1 = createImg(
* 'assets/moonwalk.jpg',
* 'An astronaut walking on the moon',
* ''
* );
* img1.position(0, 0);
*
* // Load an image of an astronaut on the moon
* // and place it at the top-left of the canvas.
* // Resize the image once it's loaded.
* img2 = createImg(
* 'assets/moonwalk.jpg',
* 'An astronaut walking on the moon',
* '',
* resizeImage
* );
* img2.position(0, 0);
*
* describe('A gray square two copies of a space image at the top-left. The copy in front is smaller.');
* }
*
* // Resize img2 and keep its aspect ratio.
* function resizeImage() {
* img2.size(50, AUTO);
* }
* </code>
* </div>
*/
/**
* @param {(Number|AUTO)} [w] width of the element, either AUTO, or a number.
* @param {(Number|AUTO)} [h] height of the element, either AUTO, or a number.
* @chainable
*/
size(w, h) {
if (arguments.length === 0) {
return { width: this.elt.offsetWidth, height: this.elt.offsetHeight };
} else {
let aW = w;
let aH = h;
const AUTO$1 = AUTO;
if (aW !== AUTO$1 || aH !== AUTO$1) {
if (aW === AUTO$1) {
aW = h * this.width / this.height;
} else if (aH === AUTO$1) {
aH = w * this.height / this.width;
}
// set diff for cnv vs normal div
if (this.elt instanceof HTMLCanvasElement) {
const j = {};
const k = this.elt.getContext('2d');
let prop;
for (prop in k) {
j[prop] = k[prop];
}
this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
this.elt.style.width = aW + 'px';
this.elt.style.height = aH + 'px';
this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
for (prop in j) {
this.elt.getContext('2d')[prop] = j[prop];
}
} else {
this.elt.style.width = aW + 'px';
this.elt.style.height = aH + 'px';
this.elt.width = aW;
this.elt.height = aH;
}
this.width = aW;
this.height = aH;
if (this._pInst && this._pInst._curElement) {
// main canvas associated with p5 instance
if (this._pInst._curElement.elt === this.elt) {
this._pInst.width = aW;
this._pInst.height = aH;
}
}
}
return this;
}
}
/**
* Applies a style to the element by adding a
* <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax" target="_blank">CSS declaration</a>.
*
* The first parameter, `property`, is a string. If the name of a style
* property is passed, as in `myElement.style('color')`, the method returns
* the current value as a string or `null` if it hasn't been set. If a
* `property:style` string is passed, as in
* `myElement.style('color:deeppink')`, the method sets the style `property`
* to `value`.
*
* The second parameter, `value`, is optional. It sets the property's value.
* `value` can be a string, as in
* `myElement.style('color', 'deeppink')`, or a
* <a href="#/p5.Color">p5.Color</a> object, as in
* `myElement.style('color', myColor)`.
*
* @param {String} property style property to set.
* @returns {String} value of the property.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a paragraph element and set its font color to "deeppink".
* let p = createP('p5*js');
* p.position(25, 20);
* p.style('color', 'deeppink');
*
* describe('The text p5*js written in pink on a gray background.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a p5.Color object.
* let c = color('deeppink');
*
* // Create a paragraph element and set its font color using a p5.Color object.
* let p = createP('p5*js');
* p.position(25, 20);
* p.style('color', c);
*
* describe('The text p5*js written in pink on a gray background.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a paragraph element and set its font color to "deeppink"
* // using property:value syntax.
* let p = createP('p5*js');
* p.position(25, 20);
* p.style('color:deeppink');
*
* describe('The text p5*js written in pink on a gray background.');
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create an empty paragraph element and set its font color to "deeppink".
* let p = createP();
* p.position(5, 5);
* p.style('color', 'deeppink');
*
* // Get the element's color as an RGB color string.
* let c = p.style('color');
*
* // Set the element's inner HTML using the RGB color string.
* p.html(c);
*
* describe('The text "rgb(255, 20, 147)" written in pink on a gray background.');
* }
* </code>
* </div>
*/
/**
* @param {String} property
* @param {String|p5.Color} value value to assign to the property.
* @return {String} value of the property.
* @chainable
*/
style(prop, val) {
const self = this;
if (val instanceof Color) {
val = val.toString();
}
if (typeof val === 'undefined') {
if (prop.indexOf(':') === -1) {
// no value set, so assume requesting a value
let styles = window.getComputedStyle(self.elt);
let style = styles.getPropertyValue(prop);
return style;
} else {
// value set using `:` in a single line string
const attrs = prop.split(';');
for (let i = 0; i < attrs.length; i++) {
const parts = attrs[i].split(':');
if (parts[0] && parts[1]) {
this.elt.style[parts[0].trim()] = parts[1].trim();
}
}
}
} else {
// input provided as key,val pair
this.elt.style[prop] = val;
if (
prop === 'width' ||
prop === 'height' ||
prop === 'left' ||
prop === 'top'
) {
let styles = window.getComputedStyle(self.elt);
let styleVal = styles.getPropertyValue(prop);
let numVal = styleVal.replace(/[^\d.]/g, '');
this[prop] = Math.round(parseFloat(numVal, 10));
}
}
return this;
}
/* Helper method called by p5.Element.style() */
_translate(...args) {
this.elt.style.position = 'absolute';
// save out initial non-translate transform styling
let transform = '';
if (this.elt.style.transform) {
transform = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
transform = transform.replace(/translate[X-Z]?\(.*\)/g, '');
}
if (args.length === 2) {
this.elt.style.transform =
'translate(' + args[0] + 'px, ' + args[1] + 'px)';
} else if (args.length > 2) {
this.elt.style.transform =
'translate3d(' +
args[0] +
'px,' +
args[1] +
'px,' +
args[2] +
'px)';
if (args.length === 3) {
this.elt.parentElement.style.perspective = '1000px';
} else {
this.elt.parentElement.style.perspective = args[3] + 'px';
}
}
// add any extra transform styling back on end
this.elt.style.transform += transform;
return this;
}
/* Helper method called by p5.Element.style() */
_rotate(...args) {
// save out initial non-rotate transform styling
let transform = '';
if (this.elt.style.transform) {
transform = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
transform = transform.replace(/rotate[X-Z]?\(.*\)/g, '');
}
if (args.length === 1) {
this.elt.style.transform = 'rotate(' + args[0] + 'deg)';
} else if (args.length === 2) {
this.elt.style.transform =
'rotate(' + args[0] + 'deg, ' + args[1] + 'deg)';
} else if (args.length === 3) {
this.elt.style.transform = 'rotateX(' + args[0] + 'deg)';
this.elt.style.transform += 'rotateY(' + args[1] + 'deg)';
this.elt.style.transform += 'rotateZ(' + args[2] + 'deg)';
}
// add remaining transform back on
this.elt.style.transform += transform;
return this;
}
/**
* Adds an
* <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#attributes" target="_blank">attribute</a>
* to the element.
*
* This method is useful for advanced tasks. Most commonly-used attributes,
* such as `id`, can be set with their dedicated methods. For example,
* `nextButton.id('next')` sets an element's `id` attribute. Calling
* `nextButton.attribute('id', 'next')` has the same effect.
*
* The first parameter, `attr`, is the attribute's name as a string. Calling
* `myElement.attribute('align')` returns the attribute's current value as a
* string or `null` if it hasn't been set.
*
* The second parameter, `value`, is optional. It's a string used to set the
* attribute's value. For example, calling
* `myElement.attribute('align', 'center')` sets the element's horizontal
* alignment to `center`.
*
* @return {String} value of the attribute.
*
* @example
* <div>
* <code>
* function setup() {
* createCanvas(100, 100);
*
* // Create a container div element and place it at the top-left corner.
* let container = createDiv();
* container.position(0, 0);
*
* // Create a paragraph element and place it within the container.
* // Set its horizontal alignment to "left".
* let p1 = createP('hi');
* p1.parent(container);
* p1.attribute('align', 'left');
*
* // Create a paragraph element and place it within the container.
* // Set its horizontal alignment to "center".
* let p2 = createP('hi');
* p2.parent(container);
* p2.attribute('align', 'center');
*
* // Create a paragraph element and place it within the container.
* // Set its horizontal alignment to "right".
* let p3 = createP('hi');
* p3.parent(container);
* p3.attribute('align', 'right');
*
* describe('A gray square with the text "hi" written on three separate lines, each placed further to the right.');
* }
* </code>
* </div>
*/
/**
* @param {String} attr attribute to set.
* @param {String} value value to assign to the attribute.
* @chainable
*/
attribute(attr, value) {
//handling for checkboxes and radios to ensure options get
//attributes not divs
if (
this.elt.firstChild != null &&
(this.elt.firstChild.type === 'checkbox' ||
this.elt.firstChild.type === 'radio')
) {
if (typeof value === 'undefined') {
return this.elt.firstChild.getAttribute(attr);
} else {
for (let i = 0; i < this.elt.childNodes.length; i++) {
this.elt.childNodes[i].setAttribute(attr, value);
}
}
} else if (typeof value === 'undefined') {
return this.elt.getAttribute(attr);
} else {
this.elt.setAttribute(attr, value);
return this;
}
}
/**
* Removes an attribute from the element.
*
* The parameter `attr` is the attribute's name as a string. For example,
* calling `myElement.removeAttribute('align')` removes its `align`
* attribute if it's been set.
*
* @param {String} attr attribute to remove.
* @chainable
*
* @example
* <div>
* <code>
* let p;
*
* function setup() {
* createCanvas(100, 100);
*
* background(200);
*
* // Create a paragraph element and place it in the center of the canvas.
* // Set its "align" attribute to "center".
* p = createP('hi');
* p.position(0, 20);
* p.attribute('align', 'center');
*
* describe('The text "hi" written in black at the center of a gray square. The text moves to the left edge when double-clicked.');
* }
*
* // Remove the 'align' attribute when the user double-clicks the paragraph.
* function doubleClicked() {
* p.removeAttribute('align');
* }
* </code>
* </div>
*/
removeAttribute(attr) {
if (
this.elt.firstChild != null &&
(this.elt.firstChild.type === 'checkbox' ||
this.elt.firstChild.type === 'radio')
) {
for (let i = 0; i < this.elt.childNodes.length; i++) {
this.elt.childNodes[i].removeAttribute(attr);
}
}
this.elt.removeAttribute(attr);
return this;
}
/**
* Returns or sets the element's value.
*
* Calling `myElement.value()` returns the element's current value.
*
* The parameter, `value`, is an optional number or string. If provided,
* as in `myElement.value(123)`, it's used to set the element's value.
*
* @return {String|Number} value of the element.
*
* @example
* <div>
* <code>
* let input;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a text input and place it beneath the canvas.
* // Set its default value to "hello".
* input = createInput('hello');
* input.position(0, 100);
*
* describe('The text from an input box is displayed on a gray square.');
* }
*
* function draw() {
* background(200);
*
* // Use the input's value to display a message.
* let msg = input.value();
* text(msg, 0, 55);
* }
* </code>
* </div>
*
* <div>
* <code>
* let input;
*
* function setup() {
* createCanvas(100, 100);
*
* // Create a text input and place it beneath the canvas.
* // Set its default value to "hello".
* input = createInput('hello');
* input.position(0, 100);
*
* describe('The text from an input box is displayed on a gray square. The text resets to "hello" when the user double-clicks the square.');
* }
*
* function draw() {
* background(200);
*
* // Use the input's value to display a message.
* let msg = input.value();
* text(msg, 0, 55);
* }
*
* // Reset the input's value.
* function doubleClicked() {
* input.value('hello');
* }
* </code>
* </div>
*/
/**
* @param {String|Number} value
* @chainable
*/
value(...args) {
if (args.length > 0) {
this.elt.value = args[0];
return this;
} else {
if (this.elt.type === 'range') {
return parseFloat(this.elt.value);
} else return this.elt.value;
}
}
/**
* Calls a function when the mouse is pressed over the element.
*
* Calling `myElement.mousePressed(false)` disables the function.
*
* Note: Some mobile browsers may also trigger this event when the element
* receives a quick tap.
*
* @param {Function|Boolean} fxn function to call when the mouse is
* pressed over the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when the canvas
* // is pressed.
* cnv.mousePressed(randomColor);
*
* describe('A gray square changes color when the mouse is pressed.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*/
mousePressed(fxn) {
// Prepend the mouse property setters to the event-listener.
// This is required so that mouseButton is set correctly prior to calling the callback (fxn).
// For details, see https://github.com/processing/p5.js/issues/3087.
const eventPrependedFxn = function (event) {
this._pInst.mouseIsPressed = true;
this._pInst._activePointers.set(event.pointerId, event);
this._pInst._setMouseButton(event);
this._pInst._updatePointerCoords(event);
// Pass along the return-value of the callback:
return fxn.call(this, event);
};
// Pass along the event-prepended form of the callback.
Element._adjustListener('pointerdown', eventPrependedFxn, this);
return this;
}
/**
* Calls a function when the mouse is pressed twice over the element.
*
* Calling `myElement.doubleClicked(false)` disables the function.
*
* @param {Function|Boolean} fxn function to call when the mouse is
* double clicked over the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when the
* // canvas is double-clicked.
* cnv.doubleClicked(randomColor);
*
* describe('A gray square changes color when the user double-clicks the canvas.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*/
doubleClicked(fxn) {
Element._adjustListener('dblclick', fxn, this);
return this;
}
/**
* Calls a function when the mouse wheel scrolls over the element.
*
* The callback function, `fxn`, is passed an `event` object. `event` has
* two numeric properties, `deltaY` and `deltaX`. `event.deltaY` is
* negative if the mouse wheel rotates away from the user. It's positive if
* the mouse wheel rotates toward the user. `event.deltaX` is positive if
* the mouse wheel moves to the right. It's negative if the mouse wheel moves
* to the left.
*
* Calling `myElement.mouseWheel(false)` disables the function.
*
* @param {Function|Boolean} fxn function to call when the mouse wheel is
* scrolled over the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when the
* // mouse wheel moves.
* cnv.mouseWheel(randomColor);
*
* describe('A gray square changes color when the user scrolls the mouse wheel over the canvas.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call changeBackground() when the
* // mouse wheel moves.
* cnv.mouseWheel(changeBackground);
*
* describe('A gray square. When the mouse wheel scrolls over the square, it changes color and displays shapes.');
* }
*
* function changeBackground(event) {
* // Change the background color
* // based on deltaY.
* if (event.deltaY > 0) {
* background('deeppink');
* } else if (event.deltaY < 0) {
* background('cornflowerblue');
* } else {
* background(200);
* }
*
* // Draw a shape based on deltaX.
* if (event.deltaX > 0) {
* circle(50, 50, 20);
* } else if (event.deltaX < 0) {
* square(40, 40, 20);
* }
* }
* </code>
* </div>
*/
mouseWheel(fxn) {
Element._adjustListener('wheel', fxn, this);
return this;
}
/**
* Calls a function when the mouse is released over the element.
*
* Calling `myElement.mouseReleased(false)` disables the function.
*
* Note: Some mobile browsers may also trigger this event when the element
* receives a quick tap.
*
* @param {Function|Boolean} fxn function to call when the mouse is
* pressed over the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when a
* // mouse press ends.
* cnv.mouseReleased(randomColor);
*
* describe('A gray square changes color when the user releases a mouse press.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*/
mouseReleased(fxn) {
Element._adjustListener('pointerup', fxn, this);
return this;
}
/**
* Calls a function when the mouse is pressed and released over the element.
*
* Calling `myElement.mouseReleased(false)` disables the function.
*
* Note: Some mobile browsers may also trigger this event when the element
* receives a quick tap.
*
* @param {Function|Boolean} fxn function to call when the mouse is
* pressed and released over the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when a
* // mouse press ends.
* cnv.mouseClicked(randomColor);
*
* describe('A gray square changes color when the user releases a mouse press.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*/
mouseClicked(fxn) {
Element._adjustListener('click', fxn, this);
return this;
}
/**
* Calls a function when the mouse moves over the element.
*
* Calling `myElement.mouseMoved(false)` disables the function.
*
* @param {Function|Boolean} fxn function to call when the mouse
* moves over the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when the
* // mouse moves.
* cnv.mouseMoved(randomColor);
*
* describe('A gray square changes color when the mouse moves over the canvas.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*/
mouseMoved(fxn) {
Element._adjustListener('pointermove', fxn, this);
return this;
}
/**
* Calls a function when the mouse moves onto the element.
*
* Calling `myElement.mouseOver(false)` disables the function.
*
* @param {Function|Boolean} fxn function to call when the mouse
* moves onto the element.
* `false` disables the function.
* @chainable
*
* @example
* <div>
* <code>
* function setup() {
* // Create a canvas element and
* // assign it to cnv.
* let cnv = createCanvas(100, 100);
*
* background(200);
*
* // Call randomColor() when the
* // mouse moves onto the canvas.
* cnv.mouseOver(randomColor);
*
* describe('A gray square changes color when the mouse moves onto the canvas.');
* }
*
* // Paint the background either
* // red, yellow, blue, or green.
* function randomColor() {
* let c = random(['red', 'yellow', 'blue', 'green']);
* background(c);
* }
* </code>
* </div>
*/
mouseOver(fxn) {
Element._adjustListener('pointerover', fxn, this);
return this;
}
/**
* Calls a function when the mouse moves off the element.
*
* Calling `myElement.mouseOut(false)` disables the function.
*
* @param {Function|Boolean} fxn function to call when the mouse
* moves off the element