☰
p5.js 的 parent() 与 child() 进阶:用变量与 this 实现动态 DOM 嵌套(Tutorial 8.14 深入解析)
2026/10/13 3:08:23 网站建设 项目流程
  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

本篇技术指南围绕 Coding Train 网站归档仓库(website-archive)中 8.14 Parent and Child with Variables 教程展开,讲解 p5.js 中parent()/child()的进阶用法:如何将 JavaScript 动态生成的 DOM 元素嵌套进指定容器,并结合变量、this回调上下文与remove()清理机制,构建"点击链接→在当前段落内插入图片→一键清空"的完整交互示例。读完本文,你将掌握parent()双向调用(设置父容器 / 查询父容器)、循环创建 DOM 元素时的作用域陷阱,以及事件回调中this的正确取用方式。

教程背景与源码仓库对应关系

本教程编号为 8.14,视频日期 2015-10-26,是 HTML / CSS / DOM 系列教程(P5.JS Tutorial,系列号 8)中第 14 个主题。它的直接前置内容是 8.12 的 parent() and child()——该视频首次引入parent()与child(),演示了用createElement('li', ...)生成列表项并挂到已有列表下:

var happy = ['rainbow', 'unicorn', 'purple', 'bacteria']; function setup() { noCanvas(); var button = select('#button'); button.mousePressed(addItem); } function addItem() { var r = floor(random(0, happy.length)); var li = createElement('li', happy[r]); li.parent('happylist'); }

对应完整源码见 8.12_p5.js_parent_child/sketch.js。

8.14 则在 8.12 的基础上进一步回答一个实战问题:当父容器不是由 HTML 预先写死,而是也由 JavaScript 动态创建时,如何把后续生成的新元素挂载进去?仓库中与本教程对应的是四个递进版本目录:

  • 8.14_0_and_child()_again_with_variables/) —— 单个段落,用全局变量保存父容器
  • 8.14_1_and_child()_again_with_variables/) —— 循环生成 5 个段落,用this定位当前父容器
  • 8.14_2_and_child()_again_with_variables/) —— 用this.parent()反查父容器
  • 8.14_3_and_child()_again_with_variables/) —— 最终版:图片数组 + 清空按钮 +remove()

下面按这四步演进逐一剖析,这正是本教程"with Variables"(与变量一起使用)这一标题的完整含义。

第一步:用变量保存父容器(8.14_0)

最直观的思路:既然p段落是用createP()动态创建的,就把它的引用存进一个变量,之后随时把新元素parent(p)进去。完整代码见 8.14_0/sketch.js_and_child()_again_with_variables/sketch.js):

var p; function setup() { noCanvas(); p = createP('This is a link: '); p.style('background-color', '#CCC'); p.style('padding', '24px'); var a = createA('#', 'apples'); a.mousePressed(addPhoto); a.parent(p); } function addPhoto() { var img = createImg('apples2.jpg'); img.size(100, 100); img.parent(p); // img.parent(this); }

关键点拆解:

  1. noCanvas():本教程所有示例都不需要画布,纯 DOM 操作,因此关闭默认 canvas 创建。
  2. createP('This is a link: ')生成段落元素,用p.style('background-color', '#CCC')和p.style('padding', '24px')就地加样式,形成一个可见的"容器块"。
  3. createA('#', 'apples')生成一个链接,a.parent(p)把链接嵌套进段落内部,同时a.mousePressed(addPhoto)注册点击回调。
  4. 点击链接后addPhoto()运行:createImg('apples2.jpg')创建图片,img.size(100, 100)设定尺寸,然后img.parent(p)把图片追加到同一段落里——因为p是全局变量,回调函数里依然能拿到正确的父容器引用。

这里被注释掉的img.parent(this)是一个伏笔:在单段落场景下,this指向触发事件的链接元素,img.parent(this)会把图片挂到链接自身之下,而不是段落;这正是下一步要解决的问题。

第二步:循环创建多个容器时的作用域陷阱(8.14_1)

如果需求升级为"页面上有 5 个段落,每个段落里都有一个 apples 链接,点击哪个链接就把图片插进哪个段落",那么全局变量p就失效了——它只会保存最后一次循环的值。见 8.14_1/sketch.js_and_child()_again_with_variables/sketch.js):

// var p; function setup() { noCanvas(); for (var i = 0; i < 5; i++) { var p = createP('This is a link: '); p.style('background-color', '#CCC'); p.style('padding', '24px'); var a = createA('#', 'apples'); a.mousePressed(addPhoto); a.parent(p); } } function addPhoto() { var img = createImg('apples2.jpg'); img.size(100, 100); // img.parent(p); img.parent(this); }

注意// var p;被注释掉,p变成了setup()内部的局部变量,循环结束后其引用就丢失了。此时正确做法是:

  • 在回调函数里使用this——它代表触发mousePressed事件的元素,即被点击的那个a链接;
  • 于是img.parent(this)把图片挂到该链接内部。

这样点击第 3 个段落里的链接,图片就只出现在第 3 个段落中,实现了"按点击位置精准定位父容器"。代价是图片被放进链接元素内部,视觉上位于文字之后;如果想图片挂在段落(而不是链接)里,就需要下一步的技巧。

第三步:用this.parent()反查父容器(8.14_2)

p5.js 的 DOM 元素对象同时提供双向能力:el.parent(container)用来设置父容器,而el.parent()不带参数时用来查询当前元素的父容器。见 8.14_2/sketch.js_and_child()_again_with_variables/sketch.js):

// var p; function setup() { noCanvas(); for (var i = 0; i < 5; i++) { var p = createP('This is a link: '); p.style('background-color', '#CCC'); p.style('padding', '24px'); var a = createA('#', 'apples'); a.mousePressed(addPhoto); a.parent(p); } } function addPhoto() { var img = createImg('appleImage.jpg'); img.size(100, 100); var paragraph = this.parent(); img.parent(paragraph); }

这里的核心是var paragraph = this.parent();:

  • this是当前被点击的链接;
  • this.parent()返回链接的父元素,也就是外层那个带灰色背景的段落;
  • 再把img.parent(paragraph),图片便以段落的兄弟层级(挂在段落内部、链接之后)出现,而不是塞进链接里。

于是我们彻底摆脱了对循环内局部变量p的依赖:不管页面有多少个动态段落,回调里都能通过this→this.parent()一路反查到自己所属的容器。这比 8.14_0 的全局变量方案更健壮,也比 8.14_1 的img.parent(this)更符合"图片属于段落"的语义。

第四步:最终版——数组管理 + 一键清空(8.14_3)

最后一个版本加入两个增强:用数组收集所有生成的图片,并提供一个 HTML 中的#clear按钮批量移除。完整代码见 8.14_3/sketch.js_and_child()_again_with_variables/sketch.js),配套页面见 8.14_3/index.html_and_child()_again_with_variables/index.html):

// var p; var images = []; function setup() { noCanvas(); for (var i = 0; i < 5; i++) { var p = createP('This is a link: '); p.style('background-color', '#CCC'); p.style('padding', '24px'); var a = createA('#', 'apples'); a.mousePressed(addPhoto); a.parent(p); } var button = select('#clear'); button.mousePressed(clearStuff); } function clearStuff() { for (var i = 0; i < images.length; i++) { images[i].remove(); // images[i].hide(); } images = []; } function addPhoto() { var img = createImg('appleImage.jpg'); images.push(img); img.size(100, 100); // img.parent(p); // img.parent(this); var paragraph = this.parent(); img.parent(paragraph); }

HTML 中对应增加了一个清空按钮(位于<body>内、sketch.js 之前):

<button id="clear">clear images</button>

本版本的关键工程点:

  1. 引用登记:var images = []是全局数组,每次addPhoto()生成图片后先images.push(img)登记,方便日后批量操作。
  2. DOM 选择器:select('#clear')用 CSS 选择器语法从页面中取回 HTML 里写好的按钮,再button.mousePressed(clearStuff)挂上事件。这与系列教程 8.10 的 select 与 selectAll 主题一脉相承。
  3. remove() 与 hide() 的区别:images[i].remove()会把图片元素彻底从 DOM 中删除(页面消失且不再占用布局);注释掉的images[i].hide()则只是通过 CSS 隐藏(display: none),元素仍留在 DOM 树里。清空逻辑结束后把数组置空images = [],使状态复位。
  4. 循环生成 + 回调定位的组合:setup()里循环 5 次创建段落与链接;点击任意"apples"链接时,通过this.parent()找到所属段落,图片即插入对应段落。整个交互无需任何全局父容器变量。

从源码看 parent() / child() 的实际作用

本教程反复使用的parent()与child()是 p5.js DOM 元素(p5.Element)的核心挂载 API,其语义与原生 DOM 的appendChild类似:

  • parent(container):把当前 p5 元素挂载为container的子节点。container既可以是另一个 p5 元素对象(如p、this),也可以是字符串选择器(如'happylist',见 8.12 示例)。
  • parent()(无参):返回当前元素在 DOM 树中的父节点,本教程用它实现"从事件源反查容器"。
  • child():从父元素一侧向其中追加子元素,与parent()互为逆操作。

动态 DOM 嵌套的完整流程可以概括为一条链:createP()/createA()/createImg()等创建函数返回 p5.Element → 通过parent()或child()建立层级 → 通过mousePressed()等事件回调 +this引用在运行期继续追加新节点 → 用remove()回收。这一模式在后续教程中也被反复使用,例如 8.15 的 Drag and Drop 同样基于"动态 DOM 元素 + 事件回调"构建交互区。

运行方式

仓库中四个版本均为独立的浏览器示例,每个目录包含index.html与sketch.js:

  • 8.14_0 与 8.14_1 引用的图片为apples2.jpg,对应目录下未附带该资源(示例演示时需自行准备同名图片);
  • 8.14_2 与 8.14_3 引用的图片为appleImage.jpg,已随仓库提供(200×200 苹果图,位于对应目录下)。

运行方法:直接以任意静态服务器方式打开对应目录的index.html(例如在仓库根目录执行python3 -m http.server后访问对应路径),浏览器会从 CDN 加载 p5.js 库(index.html_and_child()_again_with_variables/index.html) 中通过https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.min.js引入)并执行sketch.js。注意示例代码使用var与function声明式风格,对应 2015 年的早期 p5.js 教程年代,与现代 ES6 写法(参见 16-javascript-es6 教程)并存于仓库中,阅读时无需刻意改写即可运行。

小结

从 8.14_0 到 8.14_3 的四个版本,完整呈现了 p5.js 动态 DOM 嵌套的三种父容器定位策略:

版本定位父容器的方式适用场景局限
8.14_0全局变量p单个父容器多容器时变量被覆盖
8.14_1事件回调中的this多容器,图片挂进链接图片层级在链接内部
8.14_2this.parent()反查多容器,图片挂进段落无
8.14_3this.parent()+ 数组 +remove()多容器 + 批量清理无

核心结论:当父容器本身是动态创建时,不要试图在循环外保存单一变量来代表"所有"父容器,而应在事件回调中利用this(被点击元素)与this.parent()(其父节点)在运行期动态定位目标容器;需要批量管理动态元素时,用数组登记引用,配合remove()即可实现完整的创建—挂载—清理生命周期。

  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

相关推荐

上一篇:Vercel CLI 域名验证(Domain Verification)错误排查指南:从 nameserver 核验到 TXT 记录
下一篇:vLLM Ascend 端到端发布流程管理技能:从 Release Checklist 到社区广播的完整实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询