UNPKG

p5

Version:

[![npm version](https://badge.fury.io/js/p5.svg)](https://www.npmjs.com/package/p5)

1,821 lines (1,774 loc) 68.9 kB
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 `&lt;div&gt;&lt;/div&gt;` element may be used as a box to * hold two pieces of text, a header and a paragraph. The * `&lt;div&gt;&lt;/div&gt;` 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