发布时间:2026/9/7 17:20:26
Web-Dev-For-Beginners 盆栽盒專案 Part 3:用 DOM 操作與 JavaScript 閉包實現植物拖曳互動 Web-Dev-For-Beginners 盆栽盒專案 Part 3用 DOM 操作與 JavaScript 閉包實現植物拖曳互動【免费下载链接】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-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer課程專案的第三堂課講解如何結合 Document Object ModelDOM與 JavaScript 閉包Closure為「虛擬盆栽盒Terrarium」專案實現純原生 JavaScript 的植物拖曳功能。讀完本篇你將掌握document.getElementById元素查找、onpointerdown/onpointermove/onpointerup指針事件生命週期、座標數學計算與閉包私有狀態管理這四項核心能力並能復現一個可拖曳的互動網頁應用。專案背景DOM 與閉包為什麼要結合操作 DOM 是網頁開發的一項關鍵技能。按照 MDN 文件的定義「Document Object Model (DOM) 元素能根據網頁文件的結構與內容來呈現物件」。現代 JavaScript 框架透過封裝大幅降低了 DOM 操作的門檻但理解底層機制仍是必要功課——本專案選擇親手管理 DOM不依賴任何框架。與 DOM 搭配的另一個核心概念是 JavaScript 閉包可以想像成一個函式被包在另一個函式中使內部函式仍能存取外部函式作用域中的變數。閉包是一個廣闊且複雜的主題本課只觸及建立盆栽盒所需的最基礎概念內部函式與外部函式建立一項關係允許內部函式存取外部函式的變數等作用域。在本專案中閉包的用途非常具體盆栽盒頁面中有 14 株植物每一株都需要獨立追蹤自己的拖曳座標。把整棵樹想像成一棵樹狀結構多樣的 Web API 讓開發者可以存取、編輯、編排 DOM 元素——而閉包則為「每一株植物」提供了互相隔離的私有狀態空間。開始之前建立 script.js 並用 defer 匯入這堂課的前置條件是盆栽盒的 HTML 與 CSS 已編輯完成對應課程中的 Part 1 與 Part 2。這堂課的新內容是新增拖曳植物進出盆栽罐的功能。在專案資料夾中新增檔案script.js並在 HTML 檔head部分匯入script src./script.js defer/script這裡有兩個關鍵細節defer屬性的作用讓 JavaScript 檔案只有在 HTML 被完全載入並解析完後才執行。你也可以改用async屬性允許 JavaScript 在解析 HTML 檔時就被執行。本專案必須選擇defer因為必須確保 HTML 元件被完整建立後才能對植物綁定拖曳事件否則document.getElementById(plant1)將取得null。檔案位置在完整解 solution/index.html 中可以看到該script標籤確實位於head區塊第 13 行緊接在樣式表引用之後且 14 株植物的img元素都分布在body中、帶有不同的idplant1到plant14這正是defer保證的「先有 DOM、後跑腳本」順序。課題一用 ID 定位 14 株植物我們要做的第一件事是建立 DOM 下、要被操控的物件的連結。在專案中有 14 株植物放在罐子外等待被拖曳。在script.js中逐一把它們交給待編輯的函式dragElementdragElement(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));發生了什麼事你正以 DOM 搜尋網頁檔內的物件以 Id 作為搜尋依據。回想 HTML 課程中的做法每一株植物有一個專屬的 Ididplant1等現在就可以使用它。在辨別完每一株植物物件後將其傳遞給dragElement函式讓該 HTML 物件變成可拖曳的。為什麼用 ID 而不是 class 作為物件參考因為 ID 在文件內是唯一的getElementById精確返回單一元素而 CSS class 的設計目的是對一組元素套用樣式若以 class 查找會得到多元素的集合不適合「逐一綁定獨立拖曳行為」的場景。閉包基礎先用一個糖果例子建立直覺在建立拖曳閉包之前先看一個最小閉包例子理解「內部函式存取外部函式變數」的語意function displayCandy(){ let candy [jellybeans]; function addCandy(candyType) { candy.push(candyType) } addCandy(gumdrops); } displayCandy(); console.log(candy) // ReferenceError: candy 是函式的本地變數全域不可見這個例子中函式displayCandy包住另一個函式addCandy新增新的糖果樣式到已存在的陣列當中。執行這段程式後在全域作用域讀取candy會被認作未定義因為它是displayCandy的本地變數——這正說明閉包建立了「外部不可見、內部可存取」的私有狀態。思考題你能讓陣列candy被全域存取嗎把它移到閉包外面試試——這時陣列變成全域變數等於取消了閉包內的存取限制。課題二建立 dragElement 閉包的外層函式現在準備建立dragElement閉包——一個包在外部函式內的內部函式組。在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; }逐行拆解元素說明terrariumElement參數由呼叫端傳入的具體 DOM 元素某株植物的imgpos1 ~ pos4四個初始值為0的本地變數用於追蹤座標pos3/pos4記錄滑鼠目前位置pos1/pos2記錄位移差值terrariumElement.onpointerdown pointerDrag;為該元素綁定pointerdown事件處理器四個pos變數是本地變數給每一個進到拖曳函式內的物件獨立操控——因為每次呼叫dragElement(...)都會產生一份新的閉包實例plant1 的pos1與 plant2 的pos1互不干擾網頁應用得以持續追蹤每個物件的位置。pointerdown是管理 DOM 的其中一項 Web API 事件當滑鼠按下或觸控開始時觸發。這個事件處理器在網頁與行動瀏覽器上皆有支援可用 CanIUse 查詢支援情況只有少數例外。思考題onclick事件處理器支援更多的瀏覽器為什麼不用它關鍵在於我們建立的互動類型——拖曳需要「按下 → 移動 → 放開」三段式追蹤onclick只能感知「按下並放開」的結果無法取得移動過程中的座標。課題三pointerDrag —— 捕捉拖曳起點terrariumElement已經準備好被拖曳了。當觸發onpointerdown事件時函式pointerDrag參與其中。在terrariumElement.onpointerdown pointerDrag;下方新增function pointerDrag(e) { e.preventDefault(); console.log(e); pos3 e.clientX; pos4 e.clientY; }這裡發生了多件事e.preventDefault()取消pointerdown的預設行為例如瀏覽器開始選取文字、啟動原生拖曳 ghost 影像讓你能操作更多的介面行為。建議實驗回到程式碼中刪掉這行並執行觀察拖曳時文字被選取、畫面出現殘影的現象。console.log(e)用瀏覽器打開index.html調查介面點擊植物時可看見事件物件e被輸出——pointerdown 事件攜帶大量資訊座標、目標元素、指針類型等。座標記錄本地變數pos3和pos4被設定為e.clientX和e.clientY取得按下植物瞬間相對於視口的 x 與 y 座標。為了全面控制植物行為拖曳過程中會持續更新這些座標。思考題如果把整個網頁應用建立在一個大閉包下程式碼會比較清楚嗎如果不是你有其他方法管理這 14 株可拖曳的植物嗎答案方向每株植物一個閉包實例正是本課程採用的模組化策略。接著在pos4 e.clientY下方增加兩行事件處理掛載到文件層級document.onpointermove elementDrag; document.onpointerup stopElementDrag;現在游標拖曳時植物跟著游標走、放開時停止。onpointermove和onpointerup與onpointerdown屬於同一族指針事件 API。為什麼把 move/up 掛在document上而不是植物元素本身拖曳過程中游標經常快速離開植物元素範圍若只監聽元素自身游標一脫離元素拖曳就中斷。掛在文件層級可以在整個視口範圍內持續追蹤。此時執行程式會看到錯誤訊息——因為函式elementDrag與stopElementDrag還沒建立。課題四elementDrag 與 stopElementDrag —— 移動計算與清理新增兩條內部函式在閉包中處理拖曳與停止拖曳事件。設計目標是可以拖曳任何一株植物並放在螢幕上的任一地方介面不強制盆栽盒的配置格式你可以自由增加、移除與移動盆栽罐內的植物。在pointerDrag宣告列的正下方新增elementDragfunction 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更新到新的 XY 座標點作為下一次 move 事件的基準最後寫入植物的 CSS 定位style.top與style.left的增量取自offsetTop/offsetLeft元素相對於定位父級的目前偏移減去位移量。你可以在 console 觀察數值在拖曳中持續更新。offsetTop和offsetLeft反映物件與其父關係物件的定位關係。父關係物件可以是任何元素只要它的定位屬性不為static。本專案的樣式表中.plant設定了position: absolute見 solution/style.css因此top/left可以直接用於定位計算。這些座標點的計算式讓你成功校整了植物與盆栽盒之間的行為。最後在elementDrag的正下方新增stopElementDragfunction stopElementDrag() { document.onpointerup null; document.onpointermove null; }這條小函式重置onpointerup與onpointermove事件讓你可以重新開始該植物的拖曳或是拖曳新的植物。思考題如果不將這些事件設為空值會發生什麼事——elementDrag會一直掛在document上持續執行即使沒有人在拖曳滑鼠任意移動都會重複計算與寫入樣式造成不必要的 DOM 更新與效能浪費甚至影響其他元素的互動。完整實作解讀從 solution 源碼印證閉包結構課程中的解 solution/script.js 完整實現了上述四個步驟可作為逐步比對的依據dragElement(document.getElementById(plant1)); // ... plant2 到 plant14 同類呼叫 ... /* A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer functions scope from an inner function. Create a closure so that you can track the dragged element*/ 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 where the Ymouse WAS - where it IS pos2 pos4 - e.clientY; //reset pos3 to current location of Xmouse pos3 e.clientX; //reset pos4 to current location of Ymouse 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; } }從源碼結構可以觀察到閉包成立的完整條件pointerDrag、elementDrag、stopElementDrag三個內部函式都宣告在dragElement函式體內它們直接讀寫外層函式的本地變數pos1~pos4與參數terrariumElement每個內部函式都被賦值為事件處理器onpointerdown等在dragElement返回後仍然存活並持續引用這些外部變數——這正是「閉包函式其詞法環境引用」的語意。對應到 HTML 側solution/index.html 中 14 個img classplant idplantN元素分佈在左右兩個#left-container/#right-container區塊中中間是由純 CSS 繪製的玻璃罐#terrarium內的.jar-walls、.dirt等元素樣式見 solution/style.css。拖曳完成後植物可以被放置在視口中任意位置形成最終的盆栽盒畫面挑戰為閉包加入新的事件處理器課程的挑戰題是新增新的事件處理器到閉包中讓你能對植物做更多事情。舉例來說雙擊植物讓它排列到最上層操作z-index。發揮創意即可——由於每個閉包實例都綁定各自的元素新增如ondblclick這類處理器的成本極低。複習與自學在螢幕上拖曳物件看似簡單但依照不同的目的與實現方法會遭遇到不同的問題。可以嘗試的方向HTML Drag and Drop API本課程刻意沒有使用它而是自訂 pointer 事件來建立不一樣的實現方法。可以試著將原生拖曳 API 套用到專案中比較兩者的差異draggable、dragstart/dragover/drop等Pointer Events 規範W3C 的 Pointer Events 文件與 MDN 的 Pointer events 參考是深入pointerdown/pointermove/pointerup的正式途徑瀏覽器相容性養成用 CanIUse 檢查網頁 API 瀏覽器相容性的習慣本專案的onpointerdown系列事件在主流網頁與行動瀏覽器上均有支援但仍有少數例外。作業用 DOM 做更多事本課配套作業是用 DOM 做更多事調查其中一項 DOM 的元素從 MDN 的 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),仅供参考

相关新闻

2026/9/7 17:20:26

京东商品评论API接口调用全攻略:从申请到数据落库

京东平台商品评论API接口技术指南做过电商数据采集的朋友应该都熟悉这个场景:领导扔过来一个任务,“把京东商品评论抓下来分析一下”,第一反应是写个爬虫去解析页面,结果折腾半天发现京东的反爬越来越狠,验证码一个接一…

2026/9/7 17:20:26

FAT文件系统取证实战:从磁盘镜像手工提取隐藏文件

BUUCTF Misc 的题我刷了不少,大部分时候拿到手就是一张图、一个压缩包或者一段流量,但“金3fat”这道题属于典型的“文件系统取证”方向,光靠眼睛看和 strings 扫一遍是出不来 flag 的。这道题名字里那个“fat”其实已经给了很大提示&#xf…

2026/9/7 17:15:25

单片机毕设选题推荐:基于 STM32 单片机的环境数据采集与继电器驱动控制系统设计 基于 STM32 单片机的温室阈值配置与自动报警系统设计(010507)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/9/7 22:26:14

NVM管理Node环境与国内镜像加速全攻略

1. 为什么选择NVM管理Node环境?在Mac上管理Node.js版本一直是个让人头疼的问题。我见过太多开发者因为直接安装Node导致版本混乱,项目跑不起来又找不到原因。NVM(Node Version Manager)就是为解决这个问题而生的工具,它…

2026/9/7 22:26:14

chatgpt赋能python:Python字体大小设置快捷键:让你的编程更加高效!

字体大小设置快捷键:让你的编程更加高效!要是你身为一名编程工程师, 那必然晓得常会耗费时间于编码以及阅读代码之上。这便表征着你必备维持视力清晰且得具备能够专注许久之长时专注能力。于这般时候, 字体大小设置之快捷键即为你绝不可少之得力助手&…

2026/9/7 22:26:14

APP逆向实战:山姆会员商店APP 协议数据采集

声明本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口 等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 有相关问题请第一时间点击头像看简介或…

2026/9/7 22:21:14

告别专利撰写困境!这款AI工具让技术创新保护变得简单高效

不管是科研人员、技术开发者,还是企业知识产权从业者,在专利申请工作中总会遇到各种棘手问题: 不懂如何从项目文件提炼创新点,技术方案描述不规范不知道权利要求怎么写,写窄了被规避,写宽了被无效缺乏系统…

2026/9/7 0:47:43

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/7 0:14:19

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/7 0:14:17

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/7 0:03:36

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

这次我们来看一个把目标检测算法和桌面端工具结合得很典型的项目:基于 YOLOv8 PyQt5 的麦穗稻穗检测识别系统。这个项目本身不是新概念,但它的价值在于落地形态很完整。YOLOv8 负责核心的麦穗稻穗目标检测,PyQt5 负责提供可视化的桌面交互界…

2026/9/7 0:03:36

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

简介:UL 1642是锂电池安全领域的重要规范,本中文版资源适合锂电池制造商、检测机构工程师及产品认证相关人员阅读,用于理解电池在设计与制造层面的安全要求、测试方法与合规要点。资源共1个PDF文件,压缩包大小834KB,便…

2026/9/7 0:03:36

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

简介:BS EN 13814-1:2019是英国采纳欧洲标准EN 13814-1:2019的正式版本,由BSI标准出版,重点规定游乐设施和游乐设备在设计与制造环节的安全准则,与BS EN 13814-2:2019、BS EN 13814-3:2019共同取代旧版BS EN 13814:2004。该标准面…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/6 19:33:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/6 10:19:40

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…