当前位置: 首页 > article >正文

蓝桥杯 - 中等 - 新手引导

要求

代码

// 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]) return
  document.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) return
  introduceTitle.innerText = config[curIndex].title
  introduceDesc.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 <= viewHeight
  if (!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:END

  if (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 } = boundingClientRect
  clone.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 参数值是需要添加的dom
  target.parentNode.appendChild(clone)
  //  TODO:END
}

// 移除元素
function removeTarget() {
  //  TODO:待补充代码
  // dom 移除的操作 :remove()
  if(clone){
    clone.remove()
  }

  //  TODO:END
}

// 获取元素整体的大小和位置 (注意:元素占据的位置要把里面的子元素计算在内,即如果里面有子元素是绝对定位且大小超出父元素,也要把这个子元素超出的部分计算在内)
function getDomWholeRect(dom) {
  if (!dom || !dom.getBoundingClientRect) return
  let 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 = width
  customClientRect.height = height
  customClientRect.x = x
  customClientRect.y = y

  function getPlaceholderSize(dom) {
    if (!dom || !dom.getBoundingClientRect) return
    let 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
}

补充

总结

  • 读题寻找题干已有的变量。
  • 不惧困难。

http://www.kler.cn/a/592220.html

相关文章:

  • React初学分享 事件绑定 组价通信 useState useEffect
  • Django 中@login_required 配置详解
  • 【深度学习】多目标融合算法(五):定制门控网络CGC(Customized Gate Control)
  • OpenBMC:BmcWeb添加路由4 设置method
  • MySQL 进阶学习文档
  • gralloc1_perform具体在干什么
  • 大语言模型的多垂类快速评估与 A/B 测试
  • 云原生服务网格:微服务通讯的量子纠缠革命
  • 【实用部署教程】olmOCR智能PDF文本提取系统:从安装到可视化界面实现
  • 计算机网络——总结
  • 分布式的消息流平台之Pulsar
  • 阿里云平台服务器操作以及发布静态项目
  • VBA常见的知识都有哪些,让AI编写的VBA经常 报错,所以VBA的基础还是要学习的
  • 西门子PLC
  • 88页手册上线 | 企业级本地私有化DeepSeek实战指南
  • 个人学习编程(3-19) leetcode刷题
  • 笔记本运行边缘计算
  • 【Qt】private槽函数可以被其他类中的信号连接
  • 算法岗学习路线
  • 【Python】12、函数-02