聊聊Web前端开发中关于JavaScript里closest()的一些基础知识。您是否曾经遇到过在JavaScript中找到DOM节点的父节点的问题,但是不确定是否要遍历多少个层次才能到达它?让我们来看一下这个HTML:
<div data-id="123"> <button>Click me</button> </div>
那很简单,对吧?假设您要data-id
在用户单击按钮后获取的值:
var button = document.querySelector("button"); button.addEventListener("click", (evt) => { console.log(evt.target.parentNode.dataset.id); // prints "123" });
在这种情况下,Node.parentNode API就足够了。它的作用是返回给定元素的父节点。在上面的示例中,evt.target
单击了按钮;它的父节点是具有data属性的div。
但是,如果HTML结构嵌套得更深呢?根据其内容,它甚至可以是动态的。
<div data-id="123"> <article> <header> <h1>Some title</h1> <button>Click me</button> </header> <!-- ... --> </article> </div>
通过添加更多HTML元素,我们的工作变得更加困难。当然,我们可以做类似的事情element.parentNode.parentNode.parentNode.dataset.id,但是来吧……那不是优雅,可重用或可扩展的。
旧方法:使用while-loop
一种解决方案是利用while循环,直到找到父节点为止。
function getParentNode(el, tagName) { while (el && el.parentNode) { el = el.parentNode; if (el && el.tagName == tagName.toUpperCase()) { return el; } } return null; }
从上面再次使用相同的HTML示例,它看起来像这样:
var button = document.querySelector("button"); console.log(getParentNode(button, 'div').dataset.id); // prints "123"
该解决方案远非完美。想象一下,如果您要使用ID或类或任何其他类型的选择器,而不是标签名。至少它允许在父级和我们的源之间有可变数量的子节点。
还有jQuery
过去,如果您不想处理上面为每个应用程序编写的上述函数(让我们成为现实,谁想要的?),那么像jQuery这样的库就派上用场了(它仍然可以)。它提供了.closest()一种确切的方法:
$("button").closest("[data-id='123']")
新方法:使用 Element.closest()
即使jQuery仍然是一种有效的方法(嘿,我们当中有些人对此很执着),但是仅将这一种方法添加到项目中就太过头了,特别是如果您可以使用本机JavaScript拥有同样的方法。
这就是起作用的地方Element.closest:
var button = document.querySelector("button"); console.log(button.closest("div")); // prints the HTMLDivElement
好了!这很容易,而且没有任何库或额外的代码。
Element.closest()允许我们遍历DOM,直到获得与给定选择器匹配的元素。令人敬畏的是,我们可以传递也要给Element.querySelector或的任何选择器Element.querySelectorAll。它可以是ID,类,数据属性,标签等。
element.closest("#my-id"); // yep element.closest(".some-class"); // yep element.closest("[data-id]:not(article)") // hell yeah
如果Element.closest根据给定的选择器找到父节点,则它会与返回相同的方式 document.querySelector。否则,如果找不到父项,它将返回null,从而易于使用if条件:
var button = document.querySelector("button"); console.log(button.closest(".i-am-in-the-dom")); // prints HTMLElement console.log(button.closest(".i-am-not-here")); // prints null if (button.closest(".i-am-in-the-dom")) { console.log("Hello there!"); } else { console.log(":("); }
准备好一些真实的例子了吗?我们走吧!
用例1:下拉列表
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <meta charset="utf-8"> <meta name='viewport' content='width=device-width, initial-scale=1'> <title>CodePen - Dropdown Example with `Element.closest`</title> <link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" /> <link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" /> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <title>CodePen - Dropdown Example with `Element.closest`</title> <script> if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); } </script> <style> html { font-size: 15px; } html, body { margin: 0; padding: 0; min-height: 100%; } body { height:100%; display: flex; flex-direction: column; } .referer-warning { background: black; box-shadow: 0 2px 5px rgba(0,0,0, 0.5); padding: 0.75em; color: white; text-align: center; font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif; line-height: 1.2; font-size: 1rem; position: relative; z-index: 2; } .referer-warning h1 { font-size: 1.2rem; margin: 0; } .referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */ </style> </head> <body class=""> <div id="result-iframe-wrap" role="main"> <iframe id="result" srcdoc=" <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <title>CodePen - Dropdown Example with `Element.closest`</title> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600&amp;display=swap'> <style> * { box-sizing: border-box; } body { font: normal 14px/1.4 "Source Sans Pro", sans-serif; background-color: #D9E2EC; color: #334E68; margin: 2rem; } /* Navigation */ .main-navigation { background-color: white; padding: 1.5rem 2rem; border-radius: .3em; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05); display: flex; align-items: center; } .main-navigation.is-expanded { border-bottom-left-radius: 0; } .main-navigation > .entry { font: inherit; color: inherit; text-decoration: unset; border: unset; background-color: unset; padding: unset; font-weight: 600; cursor: pointer; margin-right: 2rem; transition: color .1s linear; position: relative; } .main-navigation > .entry.is-dropdown { padding-right: 1rem; } .main-navigation > .entry.is-dropdown::after { content: ""; position: absolute; width: 0; height: 0; border-top: 8px solid #2CB1BC; border-left: 5px solid transparent; border-right: 5px solid transparent; top: 7px; right: 0px; } .main-navigation > .entry:hover, .main-navigation > .entry:focus { color: #2CB1BC; } /* Dropdown */ .menu-dropdown { background-color: white; position: absolute; margin: unset; padding: 1.5rem 2rem; list-style: none; border-radius: 0 0 .3em .3em; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05); display: flex; flex-wrap: wrap; max-width: 650px; border-top: 3px solid #2CB1BC; } .menu-dropdown.is-hidden { display: none; } .menu-dropdown > .item { flex: 0 0 50%; } .menu-dropdown > .item:nth-child(odd) { padding-right: 1rem; } .menu-dropdown > .item:nth-child(even) { padding-left: 1rem; } .menu-dropdown > .item:first-child, .menu-dropdown > .item:nth-child(2) { margin-bottom: 2rem; } .menu-dropdown > .item > .title { margin: unset; } .menu-dropdown > .item > .text { margin-top: unset; color: #668eb4; } .menu-dropdown > .item > .link { color: #2CB1BC; font-weight: 600; text-decoration: unset; transition: color .1s linear; } .menu-dropdown > .item > .link:hover, .menu-dropdown > .item > .link:focus { color: #334E68; } </style> <script> window.console = window.console || function(t) {}; </script> <script> if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); } </script> </head> <body translate="no" > <nav class="main-navigation"> <button type="button" class="entry is-dropdown" data-dropdown-trigger>Resources</button> <button type="button" class="entry is-dropdown" data-dropdown-trigger>Customers</button> <a href="#" class="entry">Contact</a> </nav> <ul class="menu-dropdown is-hidden"> <li class="item"> <h3 class="title">Designs</h3> <p class="text">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p> <a class="link" href="#">More</a> </li> <li class="item"> <h3 class="title">Tutorials</h3> <p class="text">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p> <a class="link" href="#">More</a> </li> <li class="item"> <h3 class="title">Templates</h3> <p class="text">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p> <a class="link" href="#">More</a> </li> <li class="item"> <h3 class="title">Mockups</h3> <p class="text">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Soluta, amet?</p> <a class="link" href="#">More</a> </li> </ul> <script src="https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js"></script> <script id="rendered-js" > var menu = document.querySelector(".menu-dropdown"); var navigation = document.querySelector(".main-navigation"); function handleClick(evt) { // Only if a click on a dropdown trigger happens, either close or open it. if (evt.target.hasAttribute("data-dropdown-trigger")) { if (menu.classList.contains("is-hidden")) { menu.classList.remove("is-hidden"); navigation.classList.add("is-expanded"); } else { menu.classList.add("is-hidden"); navigation.classList.remove("is-expanded"); } return; } // If a click happens somewhere outside the dropdown, close it. if (!evt.target.closest(".menu-dropdown")) { menu.classList.add("is-hidden"); navigation.classList.remove("is-expanded"); } } window.addEventListener("click", handleClick); //# sourceURL=pen.js </script> </body> </html> " sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true" allowpaymentrequest="true" allowfullscreen="true"> </iframe> </div> </body> </html>
我们的第一个演示是下拉菜单的基本(远非完美)实现,单击一个顶级菜单项后即可打开该下拉菜单。请注意,即使在下拉菜单中的任意位置单击或选择文本,菜单也如何保持打开状态?但是单击外部的某个位置,它将关闭。
该Element.closestAPI是什么检测之外点击。下拉菜单本身是<ul>带有.menu-dropdown类的元素,因此单击菜单外的任何位置都会将其关闭。这是因为for的值evt.target.closest(".menu-dropdown")将是null因为此类没有父节点。
function handleClick(evt) { // ... // if a click happens somewhere outside the dropdown, close it. if (!evt.target.closest(".menu-dropdown")) { menu.classList.add("is-hidden"); navigation.classList.remove("is-expanded"); }}
在handleClick
回调函数中,条件决定了要做什么:关闭下拉列表。如果单击无序列表中的其他位置,Element.closest
将找到并返回它,从而使下拉列表保持打开状态。
用例2:表格
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <meta charset="utf-8"> <meta name='viewport' content='width=device-width, initial-scale=1'> <title>CodePen - Table Example with `Element.closest`</title> <link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" /> <link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" /> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <title>CodePen - Table Example with `Element.closest`</title> <script> if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); } </script> <style> html { font-size: 15px; } html, body { margin: 0; padding: 0; min-height: 100%; } body { height:100%; display: flex; flex-direction: column; } .referer-warning { background: black; box-shadow: 0 2px 5px rgba(0,0,0, 0.5); padding: 0.75em; color: white; text-align: center; font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif; line-height: 1.2; font-size: 1rem; position: relative; z-index: 2; } .referer-warning h1 { font-size: 1.2rem; margin: 0; } .referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */ </style> </head> <body class=""> <div id="result-iframe-wrap" role="main"> <iframe id="result" srcdoc=" <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <title>CodePen - Table Example with `Element.closest`</title> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Open+Sans:400,700&amp;display=swap'> <style> body { margin: unset; font: normal 14px/1.5 "Open Sans", sans-serif; color: #444; background-color: lightblue; padding: 1rem; } input[type="checkbox"] { margin: unset; } table { max-width: 70vw; margin: auto; border-collapse: collapse; box-shadow: 0 10px 30px rgba(0, 0, 0, .1); } th { text-align: left; padding: 10px; background-color: rgba(0, 0, 0, .15); } td { background-color: white; padding: 10px; } tr:not(:last-of-type) td { border-bottom: 1px solid #ddd; } </style> <script> window.console = window.console || function(t) {}; </script> <script> if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); } </script> </head> <body translate="no" > <table> <thead> <tr> <th></th> <th>Name</th> <th>Email</th> <th></th> </tr> </thead> <tbody> <tr data-userid="1"> <td> <input type="checkbox" data-action="select"> </td> <td>John Doe</td> <td>john.doe@gmail.com</td> <td> <button type="button" data-action="edit">Edit</button> <button type="button" data-action="delete">Delete</button> </td> </tr> <tr data-userid="2"> <td> <input type="checkbox" data-action="select"> </td> <td>Jane Smith</td> <td>jane@smith.com</td> <td> <button type="button" data-action="edit">Edit</button> <button type="button" data-action="delete">Delete</button> </td> </tr> <tr data-userid="3"> <td> <input type="checkbox" data-action="select"> </td> <td>George Westminster</td> <td>g.westminster@googlemail.com</td> <td> <button type="button" data-action="edit">Edit</button> <button type="button" data-action="delete">Delete</button> </td> </tr> <tr data-userid="4"> <td> <input type="checkbox" data-action="select"> </td> <td>Will Johnson</td> <td>will.johnson@yahoo.com</td> <td> <button type="button" data-action="edit">Edit</button> <button type="button" data-action="delete">Delete</button> </td> </tr> </tbody> </table> <script src="https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js"></script> <script id="rendered-js" > (function () { "use strict"; function getUserId(target) { return target.closest("[data-userid]").dataset.userid; } function handleClick(evt) { var { action } = evt.target.dataset; if (action) { let userId = getUserId(evt.target); if (action == "edit") { alert(`Edit user with ID of ${userId}`); } else if (action == "delete") { alert(`Delete user with ID of ${userId}`); } else if (action == "select") { alert(`Selected user with ID of ${userId}`); } } } window.addEventListener("click", handleClick); })(); //# sourceURL=pen.js </script> </body> </html> " sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true" allowpaymentrequest="true" allowfullscreen="true"> </iframe> </div> </body> </html>
第二个示例呈现一个显示用户信息的表,比方说它是仪表板中的组件。每个用户都有一个ID,但是我们没有显示它,而是将其另存为每个<tr>
元素的数据属性。
<table> <!-- ... --> <tr data-userid="1"> <td> <input type="checkbox" data-action="select"> </td> <td>John Doe</td> <td>john.doe@gmail.com</td> <td> <button type="button" data-action="edit">Edit</button> <button type="button" data-action="delete">Delete</button> </td> </tr> </table>
最后一列包含两个按钮,用于编辑和删除表中的用户。第一个按钮的data-action属性为edit,第二个按钮的属性为delete。当我们单击它们中的任何一个时,我们都想触发一些操作(例如向服务器发送请求),但是为此,需要用户ID。
单击事件侦听器附加到全局窗口对象,因此,每当用户单击页面上的某个位置时,都会调用回调函数handleClick。
function handleClick(evt) { var { action } = evt.target.dataset; if (action) { // `action` only exists on buttons and checkboxes in the table. let userId = getUserId(evt.target); if (action == "edit") { alert(`Edit user with ID of ${userId}`); } else if (action == "delete") { alert(`Delete user with ID of ${userId}`); } else if (action == "select") { alert(`Selected user with ID of ${userId}`); } } }
如果单击发生在这些按钮之一以外的其他位置,则不data-action存在属性,因此什么也没有发生。但是,单击任一按钮时,将确定操作(顺便说一句,称为事件委托),并且下一步,将通过调用来检索用户ID getUserId:
function getUserId(target) { // `target` is always a button or checkbox. return target.closest("[data-userid]").dataset.userid; }
该函数期望将DOM节点作为唯一参数,并在调用时用于Element.closest查找包含按下按钮的表行。然后data-userid,它返回该值,该值现在可用于将请求发送到服务器。
用例3:React中的表
让我们继续使用表格示例,看看如何在React项目中处理它。这是返回表的组件的代码:
function TableView({ users }) { function handleClick(evt) { var userId = evt.currentTarget .closest("[data-userid]") .getAttribute("data-userid"); // do something with `userId` } return ( <table> {users.map((user) => ( <tr key={user.id} data-userid={user.id}> <td>{user.name}</td> <td>{user.email}</td> <td> <button onClick={handleClick}>Edit</button> </td> </tr> ))} </table> ); }
我发现这种用例经常出现-映射一组数据并将其显示在列表或表中,然后允许用户对其进行处理是相当普遍的。许多人使用嵌入式箭头功能,如下所示:
<button onClick={() => handleClick(user.id)}>Edit</button>
虽然这也是解决问题的有效方法,但我更喜欢使用该data-userid
技术。内联箭头功能的缺点之一是,每次React重新渲染列表时,它都需要再次创建回调函数,从而在处理大量数据时可能导致性能问题。
在回调函数中,我们只需提取目标(按钮)并获取<tr>
包含该data-userid
值的父元素来处理事件。
function handleClick(evt) { var userId = evt.target .closest("[data-userid]") .getAttribute("data-userid"); // do something with `userId` }
用例4:模态
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <meta charset="utf-8"> <meta name='viewport' content='width=device-width, initial-scale=1'> <title>CodePen - Modal Example with `Element.closest`</title> <link rel="stylesheet" media="screen" href="https://static.codepen.io/assets/fullpage/fullpage-4de243a40619a967c0bf13b95e1ac6f8de89d943b7fc8710de33f681fe287604.css" /> <link href="https://fonts.googleapis.com/css?family=Lato:300,400,400italic,700,700italic,900,900italic" rel="stylesheet" /> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <title>CodePen - Modal Example with `Element.closest`</title> <script> if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); } </script> <style> html { font-size: 15px; } html, body { margin: 0; padding: 0; min-height: 100%; } body { height:100%; display: flex; flex-direction: column; } .referer-warning { background: black; box-shadow: 0 2px 5px rgba(0,0,0, 0.5); padding: 0.75em; color: white; text-align: center; font-family: 'Lato', 'Lucida Grande', 'Lucida Sans Unicode', Tahoma, Sans-Serif; line-height: 1.2; font-size: 1rem; position: relative; z-index: 2; } .referer-warning h1 { font-size: 1.2rem; margin: 0; } .referer-warning a { color: #56bcf9; } /* $linkColorOnBlack */ </style> </head> <body class=""> <div id="result-iframe-wrap" role="main"> <iframe id="result" srcdoc=" <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <link rel="apple-touch-icon" type="image/png" href="https://static.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png" /> <meta name="apple-mobile-web-app-title" content="CodePen"> <link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico" /> <link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111" /> <title>CodePen - Modal Example with `Element.closest`</title> <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Gelasio:400,700&amp;display=swap'> <style> body { margin: unset; font: normal 16px/1.5 Gelasio, serif; text-align: center; } .open-modal { font-family: inherit; font-size: 23px; background-color: tomato; border: none; font-weight: bold; padding: .5rem 4rem; border-radius: .2rem; margin-top: 3rem; cursor: pointer; } .open-modal:hover { background-color: black; color: white; } .modal-outer { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(200, 200, 200, .4); display: flex; align-items: center; justify-content: center; } .modal-outer.is-hidden { display: none; } .modal-inner { background-color: white; position: relative; box-shadow: 0 10px 20px -10px rgba(0, 0, 0, .1); width: 100%; max-width: 70vw; max-height: 50vh; border-radius: .5rem; padding: 2rem; box-sizing: border-box; text-align: center; } .modal-close { position: absolute; top: 0; right: 0; font-size: 2rem; background-color: transparent; border: none; margin: unset; width: 50px; height: 50px; cursor: pointer; } .modal-close:hover, .modal-close:focus { color: tomato; } </style> <script> window.console = window.console || function(t) {}; </script> <script> if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); } </script> </head> <body translate="no" > <button type="button" class="open-modal">Open modal</button> <div class="modal-outer is-hidden"> <div class="modal-inner"> <button type="button" class="modal-close">&times;</button> <h1>Modal content</h1> <p>You can click anywhere outside the modal to close it.<br>Alternatively, there's the close button in the upper right corner.</p> </div> </div> <script src="https://static.codepen.io/assets/common/stopExecutionOnTimeout-157cd5b220a5c80d4ff8e0e70ac069bffd87a61252088146915e8726e5d9f147.js"></script> <script id="rendered-js" > (function () { "use strict"; var modal = document.querySelector(".modal-outer"); var open = document.querySelector(".open-modal"); var close = modal.querySelector(".modal-close"); function handleModalOpen() { modal.classList.remove("is-hidden"); } function handleModalClose() { modal.classList.add("is-hidden"); } function handleModalClick(evt) { if (!evt.target.closest(".modal-inner")) { handleModalClose(); } } function handleKeyDown(evt) { if (evt.key == "Escape" && !modal.classList.contains("is-hidden")) { handleModalClose(); } } open.addEventListener("click", handleModalOpen); close.addEventListener("click", handleModalClose); modal.addEventListener("click", handleModalClick); window.addEventListener("keydown", handleKeyDown); })(); //# sourceURL=pen.js </script> </body> </html> " sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-scripts allow-top-navigation-by-user-activation" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr" allowTransparency="true" allowpaymentrequest="true" allowfullscreen="true"> </iframe> </div> </body> </html>
最后一个示例是另一个我可以肯定的组件:模态。模态的实现通常具有挑战性,因为它们需要提供许多功能,同时要易于访问和(理想情况下)美观。
我们要重点关注如何关闭模式。在此示例中,可以通过按下Esc键盘,单击模态中的按钮或单击模态之外的任何位置来实现。
在我们的JavaScript中,我们想听模式中某处的点击:
var modal = document.querySelector(".modal-outer"); modal.addEventListener("click", handleModalClick);
默认情况下,通过.is-hidden实用程序类隐藏模式。只有当用户单击红色的大按钮时,模态才会通过删除此类而打开。一旦打开了模态,单击它的任何地方(关闭按钮除外)都不会无意间将其关闭。事件侦听器回调函数负责:
function handleModalClick(evt) { // `evt.target` is the DOM node the user clicked on. if (!evt.target.closest(".modal-inner")) { handleModalClose(); } }
evt.target是被单击的DOM节点,在此示例中,是MODE后面的整个背景<div class="modal-outer">。此DOM节点不在内<div class="modal-inner">,因此Element.closest()可以使所有想要的气泡冒泡,而找不到它。条件将对此进行检查并触发handleModalClose功能。
单击节点内的某个位置(例如标题),将成<div class="modal-inner">为父节点。在这种情况下,条件不是真实的,将模式保留为打开状态。
关于浏览器支持…
与任何酷的“新”JavaScript API一样,需要考虑对浏览器的支持。好消息是,Element.closest它并不是一个新事物,并且已经在相当长的一段时间内在所有主要浏览器中得到支持,支持率高达94%。我会说这可以在生产环境中安全使用。
唯一不提供任何支持的浏览器是Internet Explorer(所有版本)。如果您必须支持IE,那么使用jQuery方法可能会更好。
如您所见,有一些非常可靠的用例Element.closest。过去,像jQuery这样使我们相对容易使用的库现在可以与原始JavaScript一起使用。
得益于良好的浏览器支持和易于使用的API,我在许多应用程序中都严重依赖此小方法,并且尚未感到失望。
网友评论文明上网理性发言 已有0人参与
发表评论: