如何添加 p5.js 社区库,例如 p5.clickable 和 p5.gui 以在 React 项目中使用?
How to add p5.js community libraries such as p5.clickable and p5.gui to use in React projects?
目前我正在使用以下样板来创建我在 react-p5 (https://www.npmjs.com/package/react-p5) 中概述的 p5 草图。我
import React from "react";
import Sketch from "react-p5";
function App() {
let setup = (p5, canvasParentRef) => {
};
let draw = (p5) => {
};
return (
<div className="App">
<Sketch setup={setup} draw={draw} className="App" />
</div>
);
}
export default App;
我尝试重写库以匹配上面的格式,然后导出它们并导入为“import Clickable from './clickable.js'
”,但没有成功。
我该如何将这些库添加到我的 React 应用程序中?我仍在寻找通过 React 的方法,但这个问题对我来说有点障碍。
不幸的是,大多数 p5.js 附加组件都不是设计良好的 JavaScript 库。现代 JavaScript 库作为导出特定 functions/classes/variables 的模块发布,然后您可以导入这些模块。 are ways 构造 JavaScript 模块,使它们支持不同的包含它们的方式(包括经典的 <script />
标签导入、ES6 import
语句或 require()
功能),但许多附加组件,例如 p5.clickable 不这样做。此外,p5.clickable 还遭受了依赖 p5.js 库函数 (rect/fill/stroke/etc.) 的不幸设计决策的影响。然而,通过一些适度的更改,我能够让它在 React 应用程序中工作:
p5.clickable.js 的修改版本
//Determines if the mouse was pressed on the previous frame
var cl_mouseWasPressed = false;
//Last hovered button
var cl_lastHovered = null;
//Last pressed button
var cl_lastClicked = null;
//All created buttons
var cl_clickables = [];
//This function is what makes the magic happen and should be ran after
//each draw cycle.
// EDIT: Anywhere p5 was referenced, now using global.p5
global.p5.prototype.runGUI = function () {
for (let i = 0; i < cl_clickables.length; ++i) {
if (cl_lastHovered !== cl_clickables[i])
cl_clickables[i].onOutside();
}
if (cl_lastHovered != null) {
if (cl_lastClicked !== cl_lastHovered) {
cl_lastHovered.onHover();
}
}
if (!cl_mouseWasPressed && cl_lastClicked !== null) {
cl_lastClicked.onPress();
}
// EDIT: Use this.mouseIsPressed instead of the global mouseIsPressed.
// hopefully this works but I haven't investigated the this binding when
// runGUI is invoked by 'post'
if (cl_mouseWasPressed && !this.mouseIsPressed && cl_lastClicked != null) {
if (cl_lastClicked === cl_lastHovered) {
cl_lastClicked.onRelease();
}
cl_lastClicked = null;
}
cl_lastHovered = null;
cl_mouseWasPressed = this.mouseIsPressed;
}
global.p5.prototype.registerMethod('post', global.p5.prototype.runGUI);
//This function is used to get the bounding size of a
//string of text for use in the 'textScaled' property
function getTextBounds(m, font, size) {
let txt = document.createElement("span");
document.body.appendChild(txt);
txt.style.font = font;
txt.style.fontSize = size + "px";
txt.style.height = 'auto';
txt.style.width = 'auto';
txt.style.position = 'absolute';
txt.style.whiteSpace = 'no-wrap';
txt.innerHTML = m;
let width = Math.ceil(txt.clientWidth);
let height = Math.ceil(txt.clientHeight);
document.body.removeChild(txt);
return [width, height];
}
//Button Class
// EDIT: Clickable now takes a p5 instance
function Clickable(p) {
if (!p) {
// EDIT: If a p5 instance is not passed to the Clickable constructor,
// fallback on global mode
p = global;
}
this.x = 0; //X position of the clickable
this.y = 0; //Y position of the clickable
this.width = 100; //Width of the clickable
this.height = 50; //Height of the clickable
this.color = "#FFFFFF"; //Background color of the clickable
this.cornerRadius = 10; //Corner radius of the clickable
this.strokeWeight = 2; //Stroke width of the clickable
this.stroke = "#000000"; //Border color of the clickable
this.text = "Press Me"; //Text of the clickable
this.textColor = "#000000"; //Color for the text shown
this.textSize = 12; //Size for the text shown
this.textFont = "sans-serif"; //Font for the text shown
this.textScaled = false; //Scale the text with the size of the clickable
// image options
this.image = null; // image object from p5loadimage()
this.tint = null; // tint image using color
this.noTint = true; // default to disable tinting
this.filter = null; // filter effect
this.updateTextSize = function () {
if (this.textScaled) {
for (let i = this.height; i > 0; i--) {
if (getTextBounds(this.text, this.textFont, i)[0] <= this.width && getTextBounds(this.text, this.textFont, i)[1] <= this.height) {
console.log("textbounds: " + getTextBounds(this.text, this.font, i));
console.log("boxsize: " + this.width + ", " + this.height);
this.textSize = i / 2;
break;
}
}
}
}
this.updateTextSize();
this.onHover = function () {
//This function is ran when the clickable is hovered but not
//pressed.
}
this.onOutside = function () {
//This function is ran when the clickable is NOT hovered.
}
this.onPress = function () {
//This function is ran when the clickable is pressed.
}
this.onRelease = function () {
//This funcion is ran when the cursor was pressed and then
//released inside the clickable. If it was pressed inside and
//then released outside this won't work.
}
this.locate = function (x, y) {
this.x = x;
this.y = y;
}
this.resize = function (w, h) {
this.width = w;
this.height = h;
this.updateTextSize();
}
this.drawImage = function(){
// EDIT: All references to p5 library functions now use the instance p
p.image(this.image, this.x, this.y, this.width, this.height);
if(this.tint && !this.noTint){
p.tint(this.tint)
} else {
p.noTint();
}
if(this.filter){
p.filter(this.filter);
}
}
this.draw = function () {
p.push();
p.fill(this.color);
p.stroke(this.stroke);
p.strokeWeight(this.strokeWeight);
p.rect(this.x, this.y, this.width, this.height, this.cornerRadius);
p.fill(this.textColor);
p.noStroke();
if(this.image){
this.drawImage();
}
p.textAlign(p.CENTER, p.CENTER);
p.textSize(this.textSize);
p.textFont(this.textFont);
p.text(this.text, this.x + this.width / 2, this.y + this.height / 2);
if (p.mouseX >= this.x && p.mouseY >= this.y &&
p.mouseX < this.x + this.width && p.mouseY < this.y + this.height) {
cl_lastHovered = this;
if (p.mouseIsPressed && !cl_mouseWasPressed) {
cl_lastClicked = this;
}
}
p.pop();
}
cl_clickables.push(this);
}
// Export Clickable globally.
// In this case it would also be trivial to change this into a legitimate
// module by simply exporting Clickable. But I wanted to demonstrate how
// this could be done with globals
global.Clickable = Clickable;
用法示例
import React from 'react';
import Sketch from 'react-p5';
import '../lib/p5.clickable-1.2.js';
const Clickable = global.Clickable;
let x = 50;
let y = 50;
let click1;
export default (props) => {
const setup = (p, canvasParentRef) => {
p.createCanvas(500, 500).parent(canvasParentRef);
// Example Clickable
click1 = new Clickable(p);
click1.locate(x, y);
click1.onHover = function () {
this.color = props.color;
this.textColor = "#FFFFFF";
this.text = "Yay!";
};
click1.onOutside = function() {
this.color = "#FFFFFF";
this.textColor= "#000000";
this.text = "Press Me";
};
};
const draw = (p) => {
p.background(0);
p.fill(props.color || 'blue');
click1.locate(x % p.width, y);
click1.draw();
x++;
};
return <Sketch setup={setup} draw={draw} />;
};
目前我正在使用以下样板来创建我在 react-p5 (https://www.npmjs.com/package/react-p5) 中概述的 p5 草图。我
import React from "react";
import Sketch from "react-p5";
function App() {
let setup = (p5, canvasParentRef) => {
};
let draw = (p5) => {
};
return (
<div className="App">
<Sketch setup={setup} draw={draw} className="App" />
</div>
);
}
export default App;
我尝试重写库以匹配上面的格式,然后导出它们并导入为“import Clickable from './clickable.js'
”,但没有成功。
我该如何将这些库添加到我的 React 应用程序中?我仍在寻找通过 React 的方法,但这个问题对我来说有点障碍。
不幸的是,大多数 p5.js 附加组件都不是设计良好的 JavaScript 库。现代 JavaScript 库作为导出特定 functions/classes/variables 的模块发布,然后您可以导入这些模块。 are ways 构造 JavaScript 模块,使它们支持不同的包含它们的方式(包括经典的 <script />
标签导入、ES6 import
语句或 require()
功能),但许多附加组件,例如 p5.clickable 不这样做。此外,p5.clickable 还遭受了依赖 p5.js 库函数 (rect/fill/stroke/etc.) 的不幸设计决策的影响。然而,通过一些适度的更改,我能够让它在 React 应用程序中工作:
p5.clickable.js 的修改版本
//Determines if the mouse was pressed on the previous frame
var cl_mouseWasPressed = false;
//Last hovered button
var cl_lastHovered = null;
//Last pressed button
var cl_lastClicked = null;
//All created buttons
var cl_clickables = [];
//This function is what makes the magic happen and should be ran after
//each draw cycle.
// EDIT: Anywhere p5 was referenced, now using global.p5
global.p5.prototype.runGUI = function () {
for (let i = 0; i < cl_clickables.length; ++i) {
if (cl_lastHovered !== cl_clickables[i])
cl_clickables[i].onOutside();
}
if (cl_lastHovered != null) {
if (cl_lastClicked !== cl_lastHovered) {
cl_lastHovered.onHover();
}
}
if (!cl_mouseWasPressed && cl_lastClicked !== null) {
cl_lastClicked.onPress();
}
// EDIT: Use this.mouseIsPressed instead of the global mouseIsPressed.
// hopefully this works but I haven't investigated the this binding when
// runGUI is invoked by 'post'
if (cl_mouseWasPressed && !this.mouseIsPressed && cl_lastClicked != null) {
if (cl_lastClicked === cl_lastHovered) {
cl_lastClicked.onRelease();
}
cl_lastClicked = null;
}
cl_lastHovered = null;
cl_mouseWasPressed = this.mouseIsPressed;
}
global.p5.prototype.registerMethod('post', global.p5.prototype.runGUI);
//This function is used to get the bounding size of a
//string of text for use in the 'textScaled' property
function getTextBounds(m, font, size) {
let txt = document.createElement("span");
document.body.appendChild(txt);
txt.style.font = font;
txt.style.fontSize = size + "px";
txt.style.height = 'auto';
txt.style.width = 'auto';
txt.style.position = 'absolute';
txt.style.whiteSpace = 'no-wrap';
txt.innerHTML = m;
let width = Math.ceil(txt.clientWidth);
let height = Math.ceil(txt.clientHeight);
document.body.removeChild(txt);
return [width, height];
}
//Button Class
// EDIT: Clickable now takes a p5 instance
function Clickable(p) {
if (!p) {
// EDIT: If a p5 instance is not passed to the Clickable constructor,
// fallback on global mode
p = global;
}
this.x = 0; //X position of the clickable
this.y = 0; //Y position of the clickable
this.width = 100; //Width of the clickable
this.height = 50; //Height of the clickable
this.color = "#FFFFFF"; //Background color of the clickable
this.cornerRadius = 10; //Corner radius of the clickable
this.strokeWeight = 2; //Stroke width of the clickable
this.stroke = "#000000"; //Border color of the clickable
this.text = "Press Me"; //Text of the clickable
this.textColor = "#000000"; //Color for the text shown
this.textSize = 12; //Size for the text shown
this.textFont = "sans-serif"; //Font for the text shown
this.textScaled = false; //Scale the text with the size of the clickable
// image options
this.image = null; // image object from p5loadimage()
this.tint = null; // tint image using color
this.noTint = true; // default to disable tinting
this.filter = null; // filter effect
this.updateTextSize = function () {
if (this.textScaled) {
for (let i = this.height; i > 0; i--) {
if (getTextBounds(this.text, this.textFont, i)[0] <= this.width && getTextBounds(this.text, this.textFont, i)[1] <= this.height) {
console.log("textbounds: " + getTextBounds(this.text, this.font, i));
console.log("boxsize: " + this.width + ", " + this.height);
this.textSize = i / 2;
break;
}
}
}
}
this.updateTextSize();
this.onHover = function () {
//This function is ran when the clickable is hovered but not
//pressed.
}
this.onOutside = function () {
//This function is ran when the clickable is NOT hovered.
}
this.onPress = function () {
//This function is ran when the clickable is pressed.
}
this.onRelease = function () {
//This funcion is ran when the cursor was pressed and then
//released inside the clickable. If it was pressed inside and
//then released outside this won't work.
}
this.locate = function (x, y) {
this.x = x;
this.y = y;
}
this.resize = function (w, h) {
this.width = w;
this.height = h;
this.updateTextSize();
}
this.drawImage = function(){
// EDIT: All references to p5 library functions now use the instance p
p.image(this.image, this.x, this.y, this.width, this.height);
if(this.tint && !this.noTint){
p.tint(this.tint)
} else {
p.noTint();
}
if(this.filter){
p.filter(this.filter);
}
}
this.draw = function () {
p.push();
p.fill(this.color);
p.stroke(this.stroke);
p.strokeWeight(this.strokeWeight);
p.rect(this.x, this.y, this.width, this.height, this.cornerRadius);
p.fill(this.textColor);
p.noStroke();
if(this.image){
this.drawImage();
}
p.textAlign(p.CENTER, p.CENTER);
p.textSize(this.textSize);
p.textFont(this.textFont);
p.text(this.text, this.x + this.width / 2, this.y + this.height / 2);
if (p.mouseX >= this.x && p.mouseY >= this.y &&
p.mouseX < this.x + this.width && p.mouseY < this.y + this.height) {
cl_lastHovered = this;
if (p.mouseIsPressed && !cl_mouseWasPressed) {
cl_lastClicked = this;
}
}
p.pop();
}
cl_clickables.push(this);
}
// Export Clickable globally.
// In this case it would also be trivial to change this into a legitimate
// module by simply exporting Clickable. But I wanted to demonstrate how
// this could be done with globals
global.Clickable = Clickable;
用法示例
import React from 'react';
import Sketch from 'react-p5';
import '../lib/p5.clickable-1.2.js';
const Clickable = global.Clickable;
let x = 50;
let y = 50;
let click1;
export default (props) => {
const setup = (p, canvasParentRef) => {
p.createCanvas(500, 500).parent(canvasParentRef);
// Example Clickable
click1 = new Clickable(p);
click1.locate(x, y);
click1.onHover = function () {
this.color = props.color;
this.textColor = "#FFFFFF";
this.text = "Yay!";
};
click1.onOutside = function() {
this.color = "#FFFFFF";
this.textColor= "#000000";
this.text = "Press Me";
};
};
const draw = (p) => {
p.background(0);
p.fill(props.color || 'blue');
click1.locate(x % p.width, y);
click1.draw();
x++;
};
return <Sketch setup={setup} draw={draw} />;
};