ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Web-Dev-For-Beginners Terrarium 第三部分实战:用原生 JavaScript 的 DOM 操作与闭包实现可拖拽交互

Web-Dev-For-Beginners Terrarium 第三部分实战:用原生 JavaScript 的 DOM 操作与闭包实现可拖拽交互 Web-Dev-For-Beginners Terrarium 第三部分实战用原生 JavaScript 的 DOM 操作与闭包实现可拖拽交互【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners本文基于 Web-Dev-For-Beginners 课程仓库中 Terrarium虚拟生态缸项目的第三讲——DOM 操作与闭包带你完整走一遍如何用原生 JavaScript 把静态的植物页面变成可交互界面从document.getElementById引用 14 株植物、用指针事件捕获坐标、到用闭包为每株植物维护独立的位置状态最后以仓库中的官方参考实现收尾。读完后你将不依赖任何框架独立实现按住植物拖动到生态缸任意位置这一完整交互。一、什么是 DOM为什么本讲选择自己管理它DOMDocument Object Model文档对象模型是 JavaScript 与 HTML 元素之间的桥梁。引用 MDN 的表述The Document Object Model (DOM) is the data representation of the objects that comprise the structure and content of a document on the web.DOM 是构成网页文档结构与内容的那些对象的数据表示。可以把 DOM 理解为一棵树它表示控制网页文档的所有方法。程序员可以使用所选语言编写各种 API通过这棵树访问、编辑、修改、重排和管理页面内容。在真实开发中正因为 DOM 操作繁琐且容易出错不少开发者会选择引入 JavaScript 框架来代为管理 DOM但本课程的立场是我们先自己动手管理它理解底层机制后再谈框架。本讲同时会引入JavaScript 闭包closure的概念——让内部函数可以访问外部函数的作用域而本讲正是用闭包来组织 DOM 操作代码。本讲要完成的目标为交互式 terrarium 项目编写 JavaScript让用户可以在页面上直接操纵拖动植物从而完成整个 terrarium 项目。二、前置条件与文件准备开始前必须满足一个前提你已经完成了前两个 terrarium 课程的 HTML 和 CSS 编写——本讲要把那份静态设计变成可交互界面。本讲结束后你将可以拖动植物在生态缸内任意移动。准备步骤在 terrarium 文件夹中新建一个名为script.js的文件把如下标签放进index.html的head部分script src./script.js defer/script这里defer属性的用意值得展开使用defer时浏览器会等 HTML 文件完全加载解析后才执行该 JavaScript。HTML 规范中还有一个async属性它允许脚本在 HTML 解析过程中就执行——但对本项目而言关键在于必须在所有 HTML 元素解析完成之前不执行拖动脚本否则document.getElementById(plant1)之类的调用会拿不到元素。仓库官方解答中的 index.html 正是这样引入脚本的script src./script.js defer/script三、第一步为 DOM 元素建立引用要操纵 DOM第一件事是建立对目标元素的引用。在 terrarium 页面中操作对象是侧边栏上待命的14 株植物。在script.js顶部写入dragElement(document.getElementById(plant1)); dragElement(document.getElementById(plant2)); dragElement(document.getElementById(plant3)); dragElement(document.getElementById(plant4)); dragElement(document.getElementById(plant5)); dragElement(document.getElementById(plant6)); dragElement(document.getElementById(plant7)); dragElement(document.getElementById(plant8)); dragElement(document.getElementById(plant9)); dragElement(document.getElementById(plant10)); dragElement(document.getElementById(plant11)); dragElement(document.getElementById(plant12)); dragElement(document.getElementById(plant13)); dragElement(document.getElementById(plant14));这里发生了什么我们通过文档查找特定id的元素——也就是说看向 DOM 以获取这些元素。还记得 HTML 第一课中给每株植物图像如idplant1都赋予了唯一 Id 吗这里的付出开始产生回报识别每个元素后把它传给即将定义的dragElement函数于是这些 HTML 元素就获得了或者说即将获得可被拖动的能力。自检为什么用 Id 而不是 CSS 类来引用元素Id 在文档中应当唯一getElementById因此总能精确定位到某一个元素而 CSS 类天生面向一组元素的样式复用选择器可能命中多个节点。若两个元素重复了同一个 IdgetElementById()也只返回第一个——这正是 Id 语义约束的体现。四、第二步理解闭包closure在写dragElement之前先用一个小例子理解闭包。闭包在一个或多个函数需要访问外部函数作用域时非常有用function displayCandy(){ let candy [jellybeans]; function addCandy(candyType) { candy.push(candyType); } addCandy(gumdrops); } displayCandy(); console.log(candy)在这个例子中displayCandy函数内部包裹了一个向已存在数组中追加新糖果类型的函数addCandy。执行这段代码后在全局作用域直接console.log(candy)会得到未定义——因为candy是displayCandy的局部变量也就是所谓闭包局部外部作用域看不到它。自检怎样才能访问到candy数组把数组声明移出闭包即可——这样它既在闭包内可用也在全局作用域可用。这也从反面说明了闭包的价值局部变量被函数记住了但不泄漏到外面。五、第三步dragElement 闭包——私有位置状态与事件入口现在创建包裹内部函数的外部函数dragElement本讲中它内部共有 3 个函数。在script.js的元素声明下方添加function dragElement(terrariumElement) { //set 4 positions for positioning on the screen let pos1 0, pos2 0, pos3 0, pos4 0; terrariumElement.onpointerdown pointerDrag; }dragElement从脚本顶部的声明中接收terrariumElement对象然后把函数内的几个局部位置变量置为0。当你为每个元素添加拖放功能时pos1~pos4就是每株植物各自独立的局部状态植物被拖进生态缸后应用必须持续跟踪它被放置的位置而这些变量正是跟踪用的坐标簿。这里闭包的作用一目了然每调用一次dragElement(...)JavaScript 就创建一套独立的pos1~pos4环境。14 株植物对应 14 个互不干扰的坐标状态——如果把这些变量放到全局任何一次拖动都会污染其他植物的数据。另外onpointerdown属于 Web API 中的 Pointer Events 家族它在指针按钮被按下、或元素被触摸时触发在绝大多数桌面与移动浏览器上都受支持。自检事件处理器onclick的跨浏览器支持更广为什么这里不用它想一想我们想捕获的交互类型click 只覆盖按下再松开的完整点击而拖拽需要在按下的瞬间就开始追踪位移——这正是onpointerdown与onclick的本质区别。三者的对比可以这样理解事件适用场景局限onclick简单按钮点击无法处理拖拽过程只反映点击与释放onpointerdown鼠标与触摸统一处理相对较新但如今支持已很完善onmousedown仅桌面鼠标完全排除移动端用户六、第四步pointerDrag 函数——阻止默认行为并捕获起点坐标当用户在植物上按下指针时onpointerdown事件启动并调用pointerDrag。按课程流程先在terrariumElement.onpointerdown pointerDrag;这一行下方添加函数的前半部分function pointerDrag(e) { e.preventDefault(); console.log(e); pos3 e.clientX; pos4 e.clientY; }紧接着在pos4 e.clientY下方再追加两行为指针的移动与抬起绑定文档级处理器完成初始化document.onpointermove elementDrag; document.onpointerup stopElementDrag;这段代码依次做了几件事e.preventDefault()阻止指针按下时浏览器默认会发生的行为例如拖选文本、触发元素默认的拖拽 ghost 等让我们对界面行为有更强的控制力。课程建议等脚本写完后再回来把这行删掉试一次观察拖动体验的变化。console.log(e)在浏览器中打开index.html检查界面点击植物就能看到被捕获的事件对象e——仔细翻阅它你会发现一次pointerdown事件携带的信息量远超想象。pos3 e.clientX、pos4 e.clientY把局部变量设置为指针按下瞬间的 x/y 坐标。之所以要精确跟踪坐标是因为拖拽过程中需要用它们计算元素的增量位移。document.onpointermove/document.onpointerup把移动与停止两个处理器挂在整个 document上而不是植物元素本身——这样即使指针移出植物图像的范围拖动依然连续松开指针时则终止拖动手势。onpointermove与onpointerup和onpointerdown同属一套指针事件 API。按课程的分步写法此刻elementDrag与stopElementDrag尚未编写逐行运行到此处界面中会出现报错这正是接下来要写它们的信号。而在最终交付的实现里这两个函数作为函数声明存在于同一闭包内部由于函数声明会被提升hoisting引用先于定义也不会报错——可以从 solution/script.js 的源码结构中验证这一点。七、第五步elementDrag 函数——坐标运算与位置更新在pointerDrag的右花括号之后添加elementDrag它负责指针移动期间的位置计算function elementDrag(e) { pos1 pos3 - e.clientX; pos2 pos4 - e.clientY; pos3 e.clientX; pos4 e.clientY; console.log(pos1, pos2, pos3, pos4); terrariumElement.style.top terrariumElement.offsetTop - pos2 px; terrariumElement.style.left terrariumElement.offsetLeft - pos1 px; }这里的运算逻辑值得逐步拆解计算位移pos1被重新赋值为pos3 - e.clientX即上一次记录的水平坐标 − 当前水平坐标得到自上一次事件以来的水平移动量pos2对垂直方向做同样的事。滚动基线把pos3、pos4重置为当前e.clientX、e.clientY为下一次pointermove事件提供新的比较基准。拖拽过程中打开控制台可以实时看到四个变量的变化。写回样式读取元素的offsetTop与offsetLeft减去刚才算出的位移量写回style.top与style.left完成一次增量移动。offsetTop和offsetLeft是相对父级定位参照物而言的元素位置该参照物可以是任何未设为static定位的上层元素。这套位移 → 更新基线 → 写回样式的循环在每次pointermove时重复执行配合浏览器逐帧刷新就形成了平滑跟随指针的拖动效果。也正因为每一步都基于上一帧重新计算位置始终能被精微调校——无论生态缸还是其中的植物。八、第六步stopElementDrag 函数——事件清理在elementDrag的右花括号之后添加最后一个内部函数完成闭包function stopElementDrag() { document.onpointerup null; document.onpointermove null; }这个函数在指针抬起时把onpointerup与onpointermove两个文档级处理器复位为null从而结束本次拖动让下一次拖动无论继续移动当前植物还是改抓另一株都能从干净的状态重新开始。自检如果不清空这两个处理器会怎样onpointermove会一直挂在 document 上持续触发elementDrag即使用户早已松手——无用的坐标计算反复执行页面任意位置的移动都可能触发样式写入浪费性能并干扰交互。设计说明本讲有意不设置 drop zone放置区——界面对把植物放哪、加多少、怎么排不做限制用户可以把植物放在屏幕任意位置自由设计自己的生态缸。九、参考实现仓库 solution 中的完整闭包结构本讲所有步骤在仓库官方解答 3-terrarium/solution/script.js 中都有完整对应。从源码结构看dragElement 函数体L23–L64 把三个内部函数完整嵌套在闭包内部与课程分步讲解一一对应function dragElement(terrariumElement) { //set 4 positions for positioning on the screen let pos1 0, pos2 0, pos3 0, pos4 0; terrariumElement.onpointerdown pointerDrag; function pointerDrag(e) { e.preventDefault(); console.log(e); // get the initial mouse cursor position for pos3 and pos4 pos3 e.clientX; pos4 e.clientY; // when the mouse moves, start the drag document.onpointermove elementDrag; // when the mouse is lifted, stop the drag document.onpointerup stopElementDrag; } function elementDrag(e) { // pos1 where the Xmouse WAS - where it IS pos1 pos3 - e.clientX; pos2 pos4 - e.clientY; //reset pos3 to current location of Xmouse pos3 e.clientX; pos4 e.clientY; console.log(pos1, pos2, pos3, pos4); // set the elements new position: terrariumElement.style.top terrariumElement.offsetTop - pos2 px; terrariumElement.style.left terrariumElement.offsetLeft - pos1 px; } function stopElementDrag() { // stop calculating when mouse is released document.onpointerup null; document.onpointermove null; } }文件顶部还保留了说明性注释引用了 MDN 对闭包的定义闭包是函数与其词法环境引用的组合……闭包让内部函数可以访问外部函数的作用域并注明创建一个闭包以便跟踪被拖动的元素同时文件头部注明了该可拖拽方案的思想来源方便读者追溯进一步学习。文件开头则是与第三节一致的 14 行dragElement(document.getElementById(plantN))调用——每次调用都会为对应植物构建一套独立的pos1~pos4私有坐标环境这正是用闭包管理每株植物状态的落地形态。十、验证成果按如下方式测试你的交互式 terrarium在浏览器中打开index.html按住任意植物不放——pointerDrag触发控制台打印事件对象移动鼠标或手指植物平滑跟随——elementDrag每帧更新top/left松开指针——stopElementDrag复位文档级处理器拖动结束换一株植物重复操作确认各植物互不干扰。若一切正常恭喜你已经用原生 JavaScript 的 DOM 操作与闭包完成了一个完全可交互的网页应用。十一、扩展挑战与自研方向课程给出两个继续深入的方向挑战在闭包中追加新的事件处理器让植物做更多事。例如双击植物时把它带到前面z-index 层级操作——鼓励自由发挥。自研调研浏览器其实提供了一整套专门的 [HTML Drag and Drop API]文档引用它面向文件拖入、固定放置区这类场景本讲想要的任意位置自由放置与其效果不完全一致因此未采用。建议在自己的项目中试一试该 API看看它适合解决什么问题。学习资源方面指针事件的细节可查阅 W3C 的 Pointer Events 规范与 MDN 的 Pointer Events 文档动手前用 CanIUse 核对所用 API 的浏览器支持情况是贯穿本课程的方法论。十二、课后作业本讲配套的作业是 DOM 元素探究作业从 DOM 接口清单中选择一个方向如classList、querySelector、IntersectionObserver、Drag and Drop API、Web Components 等按初/中/高级分层完成 300–500 词的技术分析对比它在本讲 terrarium 场景下的适用性并附一个可运行、带注释、面向初学者的代码示例。作业还给出了明确的评分维度技术理解、真实案例分析、代码示例、写作质量、批判性思维可作为自查清单。至此Terrarium 项目三部曲收官HTML 搭结构、CSS 做样式、JavaScript 的 DOM 操作与闭包赋予交互。掌握这套引用元素 → 绑定指针事件 → 用闭包隔离状态 → 增量坐标运算 → 事件清理的模式后你就拥有了实现文件拖放、看板卡片移动、图片墙重排等大量真实交互的原生能力基础。【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表