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

蓝桥杯 - 中等 - 新手引导

要求

代码

// 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
}

补充

总结

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

相关文章:

  • Oracle数据库与MySQL数据库的全面对比分析
  • SpringBoot对接DeepSeek
  • 模板方法设计模式在事件处理中的应用
  • JavaScript 金额运算精度丢失问题及解决方案
  • 【LLMs篇】05:RMSNorm
  • AI 大模型统一集成|微服务 + 认证中心:如何保障大模型 API 的安全调用!
  • docker 部署elk 设置账号密码
  • MAT 启动报错
  • MySQL数据库精研之旅第一期:开启数据管理新旅程
  • yum软件包乾坤大挪移(Yum Package Qiankun Great Migration)
  • mysql 数据库异常排查
  • 认知篇#4:YOLO评价指标及其数学原理的学习
  • 单表查询和多表查询
  • 时序分析笔记
  • PyCharm 5的Python IDE的功能(附工具下载)
  • 建筑兔零基础自学记录49|python爬取百度地图POI实战-3
  • 康谋方案 | AVM合成数据仿真验证方案
  • 优选算法系列(2.滑动窗口_下)
  • Java+Html实现前后端客服聊天
  • anythingLLM之stream-chat传参
  • 印度军方否认S-400防空系统被摧毁
  • 西安机场回应航站楼“水帘洞”事件:屋面排水系统被冰雹堵塞
  • 竞彩湃|霍芬海姆看到保级曙光,AC米兰专注于意大利杯
  • 异域拾异|大脚怪的形状:一项神秘社会学研究
  • 读图|展现城市品格,上海城市影像走进南美
  • 首批证券公司科创债来了!拟发行规模超160亿元