background

Have you all received a similar task, novice guide – highlight one element on the page and mask the rest, add a guide and the next step. The amount of work is not much, and it is added in a meal every time. But day after day, when we meet similar requirements again, do we feel very regretting that we didn’t make a universal one last time? As a program yuan, I have gone through the same road, and this road has almost gone into cement road,…

So this time I made up my mind to make a generic one for my younger brother!!

Don’t say much, on the effect ~

 

Very simple, follow me to see how to implement it!

Research and development train of thought

Core logic selection and framework decoupling convenient in mobile terminal and PC terminal can be used without r & D framework restrictions demo display selection Vue language

Technical stack: Vite + VUE-TS template details

First, generate the static code of demo. CodeFun plug-in is selected for quick generation because it does not require high precision

Select highlight element copy node the plain copy ~ Canvas generated scheme has some compatibility issues on certain models

/ * * *@description: Clones the target element *@param {HTMLElement} Element DOM node *@return {*}* /
export const cloneElement = (element: HTMLElement): Node= > {
  const clone = element.cloneNode(trueas HTMLElement
  // Gets the size of the element and its position relative to the viewport
  const rectObject = element.getBoundingClientRect()
  const { width, height, x, y } = rectObject
  clone.style.width = width + 'px'
  clone.style.height = height + 'px'
  clone.style.position = 'absolute'
  clone.style.left = x + 'px'
  clone.style.top = y + 'px'
  clone.style.margin = '0'
  
  return clone
}
Copy the code

Initialize the box and select Fixed position to cover the full screen and hide it with a translucent background and add it to the top level on the body

/ * * *@descriptionInitialize the box *@param {*}
   * @return {*}* /
  initBox() {
    this.domBox.id = 'guideBox'
    this.domBox.style.position = 'fixed'
    this.domBox.style.top = '0'
    this.domBox.style.bottom = '0'
    this.domBox.style.left = '0'
    this.domBox.style.right = '0'
    this.domBox.style.zIndex = '100'
    this.domBox.style.background = 'rgba(0,0,0,.5)'
    this.domBox.style.display = 'none' 
    document.body.appendChild(this.domBox)
  }
Copy the code

Add a guide picture to get the real position of the picture specified orientation and add a click event to the picture click to jump to the next step

// Create a guide diagram
    const tip = document.createElement('img'as HTMLImageElement
    tip.src = content
    const [top, left] = await getPlacement(selector, content, placement, this.offset)
    tip.style.position = 'absolute'
    tip.style.top = top
    tip.style.left = left
    // Click next
    tip.onclick = () = > {
      this.stepIndex = Number(this.stepIndex) + 1
      this.startStep()
    }
    this.domBox.appendChild(tip)
    // Display the box
    this.domBox.style.display = 'block'
Copy the code

The tricky part is dealing with the placement of the guide diagram and elements

 

Use antD tooltip to determine which positions the guide image can only be placed in, depending on the size of the highlighted element and the guide image. Here’s an example: LT, Left, and LB are all on the Left, so their Left attribute is reduced relative to the Left value of the highlighted element: 1/2 highlight element width minus 1/2 guide map width is absolute positioning oh

// Subtract the offset from top and the offset from top and the offset from left and the offset from right
export const placementOffsetMap: Placement.PlacementOffsetMap = {
  top({ rw, rh, cw, ch, offset }) = > {
    return [-ch - offset, rw/2 - cw/2]},topLeft({ rw, rh, cw, ch, offset }) = > {
    return [-ch - offset, 0]},topRight:  ({ rw, rh, cw, ch, offset }) = > {
    return [-ch - offset, rw - cw]
  },
  bottom({ rw, rh, cw, ch, offset }) = > {
    return [rh + offset, rw/2 - cw/2]},bottomLeft({ rw, rh, cw, ch, offset }) = > {
    return [rh + offset, 0]},bottomRight({ rw, rh, cw, ch, offset }) = > {
    return [rh + offset, rw - cw]
  },
  left({ rw, rh, cw, ch, offset }) = > {
    return [rh/2 - ch/2, -cw - offset]
  },
  leftTop({ rw, rh, cw, ch, offset }) = > {
    return [0, -cw - offset]
  },
  leftBottom({ rw, rh, cw, ch, offset }) = > {
    return [rh - ch, -cw - offset]
  },
  right({ rw, rh, cw, ch, offset }) = > {
    return [rh/2 - ch/2, rw + offset]
  },
  rightTop({ rw, rh, cw, ch, offset }) = > {
    return [0, rw + offset]
  },
  rightBottom({ rw, rh, cw, ch, offset }) = > {
    return [rh - ch, rw + offset]
  },
};
Copy the code

Next, we need to deal with the state storage, which is generally required to store the state of localStorage. The front end does the storage state check and storage data respectively before and after the boot

/ * * *@description: Before the first step *@param {*}
   * @return {*}* /
  beforeEnter() {
    return new Promise((resolve, reject) = > {
      if (this.storageKey) { resolve(! storage.get(this.storageKey))
      } else {
        resolve(true)}}}/ * * *@description: After the last step *@param {*}
   * @return {*}* /
  afterLeave() {
    return new Promise((resolve, reject) = > {
      if (this.storageKey) {
        resolve(storage.set(this.storageKey, 1))}else {
        resolve(true)}}}Copy the code

Finally, export the code in the simplest way, add the build command to execute TSC and update the tsconfig.json file

// script
"build""tsc"

// tsconfig.json
{
  "compilerOptions": {
    "module""ES6"."strict"true."outDir""./lib"."jsx""preserve"."sourceMap"true."lib": ["esnext"."dom"]."declaration"true
  },
  "include": ["src/utils/*.ts"]}Copy the code

Complete!!!!! As simple as that, you can add various parameters based on your needs: do you want animation, add non-body elements to the box, back-end storage state, DOM support for the guidance diagram, and add steps to determine what you want

Code & Use

Code NPM www.npmjs.com/package/too…

Use cases:

const guide = new Guide({
  steps: [{
    element'.section_5'.placement'bottom'.content'https://yppphoto.hibixin.com/yppphoto/8c936439588546be907df129bc48d1f0.png'
  }, {
    element'.section_7'.placement'bottomLeft'.content'https://yppphoto.hibixin.com/yppphoto/dd4a5f0a24154e36a09c67e6f8496aef.png'
  }, {
    element'.image_4'.placement'bottomRight'.content'https://yppphoto.hibixin.com/yppphoto/6114d84ed9aa425e97363abf98643813.png'}].storageKey'demo'
})

setTimeout(() = > {
  guide.startStep()
}, 2000)
Copy the code

parameter

Guide.GuideOptions

parameter type describe The default value Whether will pass
steps Guide.Step[] steps [] is
offset number Gap between highlight elements and guide chart 8px no
storageKey string Data store keys are not transmitted or stored ‘ ‘ no

Guide.Step

parameter type describe The default value Whether will pass
element HTMLElement   string   null The highlighted element
content string Tip content is now a picture is
placement Placement.PlacementEnum Cue content orientation is

Placement.PlacementEnum

export type PlacementEnum =
  | 'top'
  | 'left'
  | 'right'
  | 'bottom'
  | 'topLeft'
  | 'topRight'
  | 'bottomLeft'
  | 'bottomRight'
  | 'leftTop'
  | 'leftBottom'
  | 'rightTop'
  | 'rightBottom';
Copy the code

conclusion

so easy! Later no longer need to write novice guide everywhere is, once and for all, this story tells us, must be thinking of lazy at any time, in case of the next encounter novice guide, I can be happy to paddle

Reference:

4 ways to implement novice boot animation: juejin.cn/post/684490…

Hi~ This will be a universal beginner boot solution: juejin.cn/post/696049…