ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Terrarium 项目第三课:用原生 JavaScript 实现 DOM 拖拽与闭包状态管理

Terrarium 项目第三课:用原生 JavaScript 实现 DOM 拖拽与闭包状态管理 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 项目虚拟植物园系列的收官一课。在完成 HTML 结构第一部分与 CSS 样式第二部分之后本课将用纯 JavaScriptVanilla JS把静态页面变成可交互的植物园用户可以用鼠标或手指自由拖拽任意植物到页面的任何位置。你将系统掌握 Document Object ModelDOM的基本原理、JavaScript 闭包Closure的实战价值以及基于 Pointer Events 的完整拖拽系统实现——从坐标追踪、事件生命周期到资源清理并对照仓库中的完整可运行源码3-terrarium/solution/script.js验证每一步的真实写法。学习路线全景从理解 DOM 到完成交互本课的知识点与动手环节环环相扣课程原文用一张旅程图概括了整条学习路径基础阶段理解 DOM、学习闭包、连接元素——理解 JavaScript 如何与 HTML 通信交互阶段搭建拖拽事件、追踪坐标、处理移动——实现拖拽的核心机制打磨阶段添加清理逻辑、测试功能、完成 Terrarium——保证体验流畅、代码无泄漏。最终交付物是一个没有任何拖拽限制的交互界面没有放置区Drop Zone、没有边界约束用户拥有完全自由的创作空间可以把 14 株植物摆成任意造型。这也是课程刻意坚持的设计哲学dragElement只为拖拽服务把创造性完全交给用户。理解 DOMJavaScript 与 HTML 之间的桥梁Document Object ModelDOM是 JavaScript 与 HTML 元素通信的方式。当浏览器加载一个 HTML 页面时会在内存中创建该页面的结构化表示这就是 DOM。可以把它想象成一张家族谱系图每个 HTML 元素都是其中的一个成员JavaScript 可以访问、修改甚至重新排列它们。DOM 操作正是静态页面蜕变为交互网站的根源按钮在悬停时变色、内容无需刷新即可更新、元素可以被拖来拖去——这些全部是 DOM 操作在起作用。课程原文用一棵树状图展示了 Terrarium 页面的 DOM 结构从Document→HTML→Head/BodyBody 下挂着H1: My Terrarium、页面容器、左右两个植物容器与 Terrarium 玻璃罐容器左右容器中又分别承载img#plant1img#plant7与img#plant8img#plant14共 14 个图片元素。这与仓库中的完整页面结构一一对应见 3-terrarium/solution/index.html。上图为课程配套的 DOM 树与对应 HTML 标记的对照图示。DOM 之所以强大的原因提供结构化的方式访问页面中的任意元素支持不刷新页面即可完成动态内容更新能对点击、拖拽等用户交互做出实时响应是现代交互式 Web 应用的基石。JavaScript 闭包让每个函数拥有私人记忆JavaScript 闭包Closure可以理解为给一个函数配备带持久记忆的私有工作区。课程用了一个很形象的类比——加拉帕戈斯群岛上达尔文雀的喙各自针对特定环境特化闭包同样会特化出记住自己上下文的函数即使其父函数早已执行完毕。在 Terrarium 项目中闭包让每一株植物独立记住自己的位置。这个模式在专业 JavaScript 开发中反复出现值得透彻理解dragElement(plant1)创建闭包 AdragElement(plant2)创建闭包 B每个闭包内部各自持有私有的pos1, pos2, pos3, pos4四个坐标变量每个闭包内部各自定义pointerDrag、elementDrag、stopElementDrag三个内部函数因此植物 1 记住自己的位置与植物 2 记住自己的位置互不干扰。理解闭包的正确心态闭包是 JavaScript 的重要主题很多开发者使用多年也未必吃透全部理论细节。本课聚焦实践应用——在构建交互函数的过程中闭包会自然而然地浮现出来当你看到它如何解决真实问题后理解自然加深。动手前准备接续前两课的基础本课需要前两课的产出作为前提HTML 结构来自 3-terrarium/1-intro-to-html/README.md定义了页面骨架与 14 株植物图片CSS 样式来自 3-terrarium/2-intro-to-css/README.md负责植物、玻璃罐、导航栏等视觉表现仓库中的样式参考见 3-terrarium/solution/style.css。如果你此前未完成这两课建议先完成它们再回来继续——因为本课的任务正是把这个静态设计变得可交互。本课将要构建的四件事对所有 Terrarium 植物实现流畅的拖拽Drag-and-Drop坐标追踪系统让植物保持并记忆自己的位置完全基于原生 JavaScript 的交互式界面借助闭包模式写出干净、组织良好的代码。第一步创建并接入 JavaScript 文件Step 1创建脚本文件。在 Terrarium 项目文件夹中新建script.js。Step 2在index.html的head区域加入脚本标签script src./script.js defer/script仓库的解决方案正是在head中通过defer引入脚本见 3-terrarium/solution/index.html。为什么defer属性如此重要确保 JavaScript 等待整个 HTML 加载完成后再执行避免 JavaScript 在元素尚未就绪时就查找元素而报错保证所有植物元素在交互前均已可用比把脚本放在页面底部的方式拥有更好的性能表现。⚠️关键提示defer能避免最常见的时序问题。缺少它JavaScript 可能在 HTML 元素加载完成前就尝试访问它们从而抛出错误。第二步用 getElementById 连接 HTML 元素在让元素可拖拽之前JavaScript 必须先能在 DOM 中找到它们。课程用图书馆目录系统作类比只要拿到目录编号ID就能精确定位想要的书元素并访问其全部内容。这里使用的正是document.getElementById()。在script.js中添加如下代码为全部 14 株植物启用拖拽功能// 为全部 14 株植物启用拖拽功能 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));仓库中的 3-terrarium/solution/script.js 与课程讲解完全一致。这段代码做了什么通过唯一 ID 在 DOM 中定位每个植物元素获取每个 HTML 元素的 JavaScript 引用把每个元素传给dragElement函数下一步即将创建为每株植物准备好拖拽交互把 HTML 结构与 JavaScript 功能连接起来。为什么用 ID 而不用类名ID 为特定元素提供唯一标识而 CSS 类用于给一组元素统一设置样式。当 JavaScript 需要操作单个元素时ID 提供更高的精确度与更好的性能。进阶提示注意我们对每株植物分别调用dragElement()。这种逐个注册的方式确保每株植物获得彼此独立的拖拽行为这正是流畅用户交互的关键。 阶段自查DOM 连接在进入拖拽功能之前确认你能✅ 解释document.getElementById()如何找到 HTML 元素✅ 理解为什么每株植物需要唯一 ID✅ 描述脚本中defer属性的作用✅ 识别 JavaScript 与 HTML 是通过 DOM 相连的。快速自测如果两个元素拥有相同的 ID 会怎样为什么getElementById()只返回一个元素答案ID 本应唯一若重复则只返回第一个匹配的元素。第三步创建拖拽闭包——先理解计数器的例子在写真正的拖拽逻辑前课程先用一个极简例子演示闭包原理function createCounter() { let count 0; // 这就像是一个私有变量 function increment() { count; // 内部函数记得外层函数的变量 return count; } return increment; // 返回内部函数 } const myCounter createCounter(); console.log(myCounter()); // 1 console.log(myCounter()); // 2这个闭包模式中发生了什么创建了只存在于该闭包中的私有变量count内部函数可以访问并修改外层变量这就是闭包原理返回内部函数时它保留了与这些私有数据的连接即使createCounter()执行完毕count依然存在并记得自己的值。为什么闭包天然适合拖拽功能对 Terrarium 而言每株植物都需要保存自己当前的坐标。闭包恰好提供为每株植物独立保持私有位置变量在拖拽事件之间持久保存坐标数据避免不同拖拽元素之间的变量冲突形成干净、组织良好的代码。学习目标此刻不必完全掌握闭包的所有理论。重点在于观察它如何帮助我们组织代码、为拖拽函数管理状态。第四步构建 dragElement 主函数与坐标系统现在编写整个拖拽逻辑的核心——dragElement。把下面的函数放在植物元素注册代码之后function dragElement(terrariumElement) { // 初始化用于位置追踪的变量 let pos1 0, // 上一次鼠标 X 坐标 pos2 0, // 上一次鼠标 Y 坐标 pos3 0, // 当前鼠标 X 坐标 pos4 0; // 当前鼠标 Y 坐标 // 设置初始拖拽事件监听器 terrariumElement.onpointerdown pointerDrag; }对照仓库源码 3-terrarium/solution/script.js结构完全吻合。理解坐标系统pos1与pos2保存鼠标新旧位置的差值即移动量pos3与pos4追踪鼠标的当前坐标terrariumElement要使其可拖拽的具体植物元素onpointerdown用户开始拖拽时触发的事件。闭包模式的运作方式为每个植物元素创建私有位置变量在整个拖拽过程中保持这些变量确保每株植物独立追踪自己的坐标通过dragElement函数提供干净的接口。为什么选择 Pointer Events 而非 onclick也许你会好奇为什么用onpointerdown而不是更常见的onclick事件类型最适合的场景局限onclick简单的按钮点击无法处理拖拽只认点击与松开onpointerdown鼠标与触摸两者皆可较新但如今已被广泛支持onmousedown仅桌面鼠标把移动端用户排除在外Pointer Events 完美契合本项目的原因无论鼠标、手指还是触控笔都能顺畅工作在笔记本、平板、手机上体验一致处理的是真实连续的拖拽移动而非点一下完事带来现代 Web 应用用户期望的流畅体验。面向未来Pointer Events 是处理用户交互的现代方式。无需分别为鼠标和触摸编写两套代码一次实现两者兼得。 阶段自查事件理解✅ 为什么用 Pointer Events 而不是只依赖鼠标事件✅ 闭包变量如何在多次函数调用之间保持✅preventDefault()对流畅拖拽的作用是什么✅ 为什么把监听器挂在document上而非单个元素上现实映射回想日常使用的拖拽界面——文件拖入浏览器窗口上传、看板Kanban任务跨列移动、相册照片重新排序、移动端触摸屏上的滑动与拖拽——本课实现的就是这些交互的底层原理。第五步pointerDrag——拖拽的起点当用户按下某株植物鼠标按下或手指触摸pointerDrag立即接管捕获初始坐标并搭建拖拽系统。把它插入dragElement闭包内、terrariumElement.onpointerdown pointerDrag;这一行之后function pointerDrag(e) { // 阻止浏览器默认行为如文本选中 e.preventDefault(); // 捕获初始鼠标/触摸位置 pos3 e.clientX; // 拖拽开始时的 X 坐标 pos4 e.clientY; // 拖拽开始时的 Y 坐标 // 为拖拽过程注册事件监听器 document.onpointermove elementDrag; document.onpointerup stopElementDrag; }仓库源码 3-terrarium/solution/script.js 与之完全一致。逐步拆解阻止可能干扰拖拽的浏览器默认行为捕获用户开始拖拽时的精确坐标注册持续拖拽移动的事件监听器准备好系统去跟踪整个文档范围内的鼠标/手指移动。理解事件阻止preventDefaulte.preventDefault()这一行对流畅拖拽至关重要。若不阻止浏览器可能拖过页面时选中文本右键拖拽时弹出上下文菜单干扰我们的拖拽行为在拖拽过程中产生视觉伪影。实验完成本课后再尝试移除e.preventDefault()观察拖拽行为的变化你会立刻理解这一行的必要性。坐标追踪系统e.clientX与e.clientY提供精确的鼠标/触摸坐标属性测量内容用途clientX相对视口的水平位置追踪左右移动clientY相对视口的垂直位置追踪上下移动理解这些坐标提供像素级精确的位置信息随用户移动指针而实时更新在不同屏幕尺寸与缩放级别下保持一致支撑流畅、响应迅速的拖拽交互。为什么要把监听器挂在 document 上注意移动与停止事件是挂在整个document上而不是植物元素上document.onpointermove elementDrag; document.onpointerup stopElementDrag;挂在文档上的原因即使鼠标移出植物元素追踪依然继续用户快速移动时拖拽不中断支持全屏幕范围的流畅拖拽处理光标移出浏览器窗口等边缘情况。⚡性能提示拖拽一结束就移除这些文档级监听器以避免内存泄漏与性能问题见下一步的stopElementDrag。第六步elementDrag——移动计算与坐标数学elementDrag负责真正的移动计算。把它添加在pointerDrag的右花括号之后function elementDrag(e) { // 计算自上一次事件以来移动的距离 pos1 pos3 - e.clientX; // 水平移动量 pos2 pos4 - e.clientY; // 垂直移动量 // 更新当前位置追踪 pos3 e.clientX; // 新的当前 X 坐标 pos4 e.clientY; // 新的当前 Y 坐标 // 将移动应用到元素位置 terrariumElement.style.top (terrariumElement.offsetTop - pos2) px; terrariumElement.style.left (terrariumElement.offsetLeft - pos1) px; }对照仓库源码 3-terrarium/solution/script.js逻辑一致源码中额外保留了console.log用于调试。理解坐标数学pos1与pos2计算自上次更新以来鼠标移动的距离pos3与pos4保存当前鼠标位置供下一次计算使用offsetTop与offsetLeft读取元素在页面上的当前位置减法逻辑让元素精确移动与鼠标移动相同的量。移动计算的分步拆解测量新旧鼠标位置的差值基于鼠标移动量计算元素应移动多远实时更新元素的 CSS 位置值保存新位置作为下一次计算的基础。可以用一个时序来理解完整流程用户从 (100, 50) 开始拖拽pointerdown触发后保存初始坐标 (pos3100, pos450) 并注册移动/松开监听器用户移动到 (110, 60)pointermove触发计算出 pos110、pos210植物相应右移 10px、下移 10px用户最终在 (120, 65) 松开pointerup触发移除监听器并为下一次拖拽复位。第七步stopElementDrag——必要的清理在elementDrag的右花括号之后添加清理函数function stopElementDrag() { // 移除文档级别的事件监听器 document.onpointerup null; document.onpointermove null; }仓库源码 3-terrarium/solution/script.js 与之相同。为什么清理如此重要防止残留事件监听器导致的内存泄漏用户松开植物后停止拖拽行为让其他元素可以独立开始拖拽为下一次拖拽操作复位系统。不做清理的后果事件监听器在拖拽结束后依然运行未使用监听器不断累积性能逐步恶化与其他元素交互时出现意外行为浏览器资源被无谓的事件处理浪费。理解 CSS 定位属性拖拽系统操作两个关键 CSS 属性属性控制内容我们的用法top距顶部的距离拖拽中的垂直定位left距左侧的距离拖拽中的水平定位关于 offset 属性的关键认知offsetTop距已定位父元素顶部的当前距离offsetLeft距已定位父元素左侧的当前距离定位上下文这些值相对最近的已定位祖先元素计算实时更新调整 CSS 属性后立刻反映新值。设计哲学这个拖拽系统有意保持高度自由——没有放置区也没有约束。用户可以把植物放在任何位置拥有对自己 Terrarium 布局的完整创作控制权。完整代码整个拖拽系统恭喜你已经用纯 JavaScript 完成了一个精密的拖拽系统。完整的dragElement闭包负责为每株植物独立维护私有位置变量处理从开始到结束的完整拖拽生命周期提供全屏幕流畅、响应迅速的移动妥当清理资源防止内存泄漏为 Terrarium 设计创造直观、富有创意的界面。仓库中的完整可运行版本就是上述函数的组装见 3-terrarium/solution/script.js配套的 HTML 与 CSS 分别在 3-terrarium/solution/index.html 与 3-terrarium/solution/style.css。测试你的交互式 Terrarium在浏览器中打开index.html按以下步骤体验点击并按住任意植物开始拖拽移动鼠标或手指观察植物平滑跟随松开植物落位到新位置尝试不同的排列组合探索这个界面。成就感时刻你刚刚用专业开发者日常使用的核心概念构建了一个完整的交互式 Web 应用。这个拖拽功能与文件上传、看板等交互界面运用的是同一套原理。 阶段自查完整系统理解✅ 闭包如何为每株植物维护独立状态✅ 为什么坐标计算对流畅移动必不可少✅ 如果忘记清理事件监听器会怎样✅ 这个模式如何扩展到更复杂的交互代码质量反思模块化设计每株植物获得闭包的独立实例事件高效性监听器干净地注册与清理跨设备支持桌面与移动端均可工作性能意识无内存泄漏、无冗余计算。扩展挑战Reset 功能Copilot Agent 挑战任务描述为 Terrarium 项目增加 Reset 功能把所有植物以平滑动画带回初始位置。要求创建一个 Reset 按钮点击后利用 CSS Transitions让所有植物在 1 秒内平滑动画回到它们最初侧边栏的位置。实现思路是页面加载时保存每株植物的原始位置点击 Reset 按钮时读取这些位置并触发动画迁移。更多创意扩展把技能再推进一步准备好让 Terrarium 更进一步试试这些扩展双击植物将其提到最前操作 z-index添加视觉反馈例如悬停植物时出现柔和的辉光实现边界限制防止植物被拖出 Terrarium 之外创建保存功能用 localStorage 记录植物位置拿起与放下植物时添加音效。学习机会每项挑战都会带给你 DOM 操作、事件处理与用户体验设计的新视角。复习与自学加深理解你已掌握 DOM 操作与闭包的基础但值得探索的还有更多。备选的拖拽实现方案本课为追求最大灵活性选择了 Pointer Events但 Web 开发界还有多种路径方案最适合学习价值HTML 拖放 API文件上传、正式拖放区理解浏览器原生能力Touch 事件移动端专属交互移动优先开发模式CSStransform属性平滑动画性能优化技巧进阶 DOM 操作主题事件委托Event Delegation为大量元素实现高效事件处理Intersection Observer检测元素进入/离开视口Mutation Observer观察 DOM 结构变化Web Components创建可复用、可封装的 UI 元素Virtual DOM 概念理解框架如何优化 DOM 更新。继续学习的技术资源技术文档MDN 的 Pointer Events 指南全面的指针事件参考、W3C Pointer Events 规范官方标准、MDN 的 JavaScript Closures 深度文章进阶闭包模式。浏览器兼容性用 CanIUse 网站核对各浏览器对特性的支持情况。练习机会用类似的拖拽机制构建一个拼图游戏创建支持拖拽任务管理的看板设计可拖拽排列照片的图片画廊测试面向移动界面的触摸手势。学习策略巩固这些概念的最佳方式就是动手实践。多构建不同的拖拽界面——每个项目都会让你对用户交互与 DOM 操作有新的认识。⚡ 接下来的 5 分钟你可以做什么打开浏览器 DevTools在控制台输入document.querySelector(body)尝试用innerHTML或textContent修改网页文本为页面上的按钮或链接添加一个点击事件用 Elements 面板检查 DOM 树结构 这一小时你可以达成的目标完成课后测验复习 DOM 操作概念创建能响应点击的交互页面用不同事件类型click、mouseover、按键练习事件处理用 DOM 操作构建简单的待办清单或计数器探索 HTML 元素与 JavaScript 对象之间的关系 一周 JavaScript 之旅完成带拖拽功能的交互式 Terrarium 项目掌握事件委托实现高效事件处理学习事件循环与异步 JavaScript通过构建带私有状态的模块练习闭包探索 Intersection Observer 等现代 DOM API不借助框架构建交互组件 一个月 JavaScript 精通计划用原生 JavaScript 创建复杂的单页应用学习现代框架React、Vue 或 Angular并与原生 DOM 对比参与开源 JavaScript 项目掌握 Web Components 与自定义元素等进阶概念用最优 DOM 模式构建高性能 Web 应用向他人讲授 DOM 操作与 JavaScript 基础课后成果总结你的 JavaScript 工具箱完成本课后你拥有了DOM 精通元素选择、属性操作与树形导航事件专长用 Pointer Events 实现跨设备交互控制闭包理解私有状态保持与函数的持久记忆交互系统从零完整实现拖拽功能性能意识事件清理与内存管理现代模式来自专业开发的代码组织技巧用户体验设计直观、响应迅速的界面。获得的专业技能你已用与以下真实产品相同的技术构建了功能Trello / 看板卡片在列间拖拽文件上传系统拖放式文件处理图片画廊照片排列界面移动应用基于触摸的交互模式。下一站你已经准备好探索 React、Vue、Angular 等现代框架——它们正是建立在这些基础的 DOM 操作概念之上。课后作业通过 DOM 元素调查作业 进一步探索 DOM 的广阔世界选择一个你感兴趣的 DOM 接口从Element.classList、Document.querySelector()、Intersection Observer API、Web Components等入门/进阶/高级选项中挑选撰写 300–500 词的分析报告对比该接口与本课拖拽技术的异同并附上可运行的代码示例——这份作业会把你对 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),仅供参考
返回列表