要求


代码
// answer.js
const xhr = new XMLHttpRequest()
xhr.overrideMimeType("application/json");
xhr.open('GET', 'config.json', true);
xhr.onreadystatechange = function () {if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {let json = JSON.parse(xhr.responseText);init(json)}
};
xhr.send();//引导组件代码片段
const introduceDomFragment = `<div id="introduce">
<div class="introduce-box"><p class="introduce-title">标题</p><p class="introduce-desc">描述</p><div class="introduce-operate"><button class="exit">跳过</button><button class="next">下一步</button></div>
</div>
</div>`const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;//当前引导索引
var curIndex = 0
//引导组件内容
var comp, introduce, introduceTitle, introduceDesc
//跳过,下一步按钮
var exit, next
//要引导的目标元素、目标元素的大小位置,显示的复制元素
var target, boundingClientRect, clone
//引导弹窗与目标元素的距离
const distance = 16//获取到引导组件的配置信息后开始引导
function init(config = []) {if (!config[curIndex]) returndocument.body.insertAdjacentHTML('afterbegin', introduceDomFragment)exit = document.querySelector('.exit')next = document.querySelector('.next')comp = document.querySelector('#introduce')introduce = document.querySelector('.introduce-box')introduceTitle = document.querySelector('.introduce-title')introduceDesc = document.querySelector('.introduce-desc')setIntorduceInfo(config)setIntorducePosition(config)exit.onclick = function (e) {e.stopPropagation()document.body.removeChild(comp)removeTarget()}next.onclick = function (e) {if (curIndex < config.length - 1) {// 下一步if (curIndex == config.length - 2) {exit.style.display = 'none'next.innerText = '完成'}curIndex++setIntorduceInfo(config)setIntorducePosition(config)} else {document.body.removeChild(comp)removeTarget()}}}// 设置引导内容
function setIntorduceInfo(config) {if (!introduceTitle || !introduceDesc) returnintroduceTitle.innerText = config[curIndex].titleintroduceDesc.innerHTML = config[curIndex].content
}// 设置引导位置
function setIntorducePosition(config) {removeTarget() // 移除上一次复制的元素 // 定义当前 target 元素target = document.querySelector(config[curIndex].target);config[curIndex].click && target.click()// 获取目标组件的位置信息boundingClientRect = getDomWholeRect(target)const { top, right, bottom, left, x, y, width, height } = boundingClientRect//是否在可视区域let isInViewPort = top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeightif (!isInViewPort) {target.scrollIntoView()// 获取目标组件的位置信息boundingClientRect = getDomWholeRect(target)}// 判断引导组件显示位置,如果未 true 表示位于左侧,否则位于右侧let isLeft = (x + width / 2) < viewWidth / 2// 设置引导组件的位置// TODO:待补充代码 目标 1// dom 实例// 像素单位introduce.style.top = boundingClientRect.y + 'px'// console.log(introduce);introduce.style.left = `${isLeft?right+distance : left-introduce.clientWidth-distance}px`// TODO:ENDif (config[curIndex].clip) {// 添加蒙层comp.style.background = 'rgba(0, 0, 0, .5)'// 复制 target 元素copyTarget()} else {//不需要蒙层comp.style.background = 'none'}
}// 复制元素
function copyTarget() {// TODO:待补充代码// target 在全局都代表目前选中的dom// 1.复制给clone > 2.设置大小位置 > 3.z-index显示在蒙层 > 4.添加到父元素 // cloneNode() 方法 创建节点的副本,并返回该副本// cloneNode()参数 true:复制本身和里面所有内容,// false:只复制本身不复制子元素clone = target.cloneNode(true)//2.设置大小位置const { top, right, bottom, left, x, y, width, height } = boundingClientRectclone.style.top = y+'px'clone.style.left = x+'px'clone.style.position = 'absolute'clone.style.width = width+'px'clone.style.height = height+'px'// 3.z-index显示在蒙层clone.style.zIndex = 9999// 4. 添加到父元素//appendChild 参数值是需要添加的domtarget.parentNode.appendChild(clone)// TODO:END
}// 移除元素
function removeTarget() {// TODO:待补充代码// dom 移除的操作 :remove()if(clone){clone.remove()}// TODO:END
}// 获取元素整体的大小和位置 (注意:元素占据的位置要把里面的子元素计算在内,即如果里面有子元素是绝对定位且大小超出父元素,也要把这个子元素超出的部分计算在内)
function getDomWholeRect(dom) {if (!dom || !dom.getBoundingClientRect) returnlet customClientRect = {x: 0,y: 0,width: 0,height: 0,top: 0,bottom: 0,left: 0,right: 0}let leftArr = []let rightArr = []let topArr = []let bottomArr = []const { x, y, width, height } = dom.getBoundingClientRect()customClientRect.width = widthcustomClientRect.height = heightcustomClientRect.x = xcustomClientRect.y = yfunction getPlaceholderSize(dom) {if (!dom || !dom.getBoundingClientRect) returnlet childRect = dom.getBoundingClientRect()topArr.push(childRect.top)rightArr.push(childRect.right)bottomArr.push(childRect.bottom)leftArr.push(childRect.left)if (dom.childNodes) {for (let i = 0; i < dom.childNodes.length; i++) {const child = dom.childNodes[i]getPlaceholderSize(child)}}}getPlaceholderSize(dom)customClientRect.top = Math.min(...topArr)customClientRect.right = Math.max(...rightArr)customClientRect.bottom = Math.max(...bottomArr)customClientRect.left = Math.min(...leftArr)return customClientRect
}




补充

总结