手把手教你实现一个引导动画

前言

最近看了一些文章,知道了实现引导动画的基本原理,所以决定来自己亲手做一个通用的引导动画类。

我们先来看一下具体的效果:点这里

原理

  1. 通过维护一个Modal实例,使用Modal的mask来隐藏掉页面的其他元素。

  2. 根据用户传入的需要引导的元素列表,依次来展示元素。展示元素的原理:通过cloneNode来复制一个当前要展示元素的副本,通过当前元素的位置信息来展示副本,并且通过z-index属性来让其在ModalMask上方展示。大致代码如下:

    const newEle = target.cloneNode(true);
    const rect = target.getBoundingClientRect();
    newEle.style.zIndex = '1001';
    newEle.style.position = 'fixed';
    newEle.style.width = `${rect.width}px`;
    newEle.style.height = `${rect.height}px`;
    newEle.style.left = `${rect.left}px`;
    newEle.style.top = `${rect.top}px`;
    this.modal.appendChild(newEle);
    
  3. 当用户点击了当前展示的元素时,则展示下一个元素。

原理听起来是不是很简单?但是其实真正实现起来,还是有坑的。比如说,当需要展示的元素不在页面的可视范围内如何处理。

当要展示的元素不在页面可视范围内,主要分为三种情况:

  1. 展示的元素在页面可视范围的上边。
  2. 展示的元素在页面可视范围的下边。
  3. 展示的元素在可视范围内,可是展示不全。

由于我是通过getBoundingClientRect这个api来获取元素的位置、大小信息的。这个api获取的位置信息是相对于视口左上角位置的(如下图)。

getBoundingClientRect示��

对于第一种情况,这个api获取的top值为负值,这个就比较好处理,直接调用window.scrollBy(0, rect.top)来将页面滚动到展示元素的顶部即可。

而对于第二、三种情况,我们可以看下图

图片

从图片我们可以看出来,当rect.top+rect.height > window.innerHeight的时候,说明展示的元素不在视野范围内,或者展示不全。对于这种情况,我们也可以通过调用window.scrollBy(0, rect.top)的方式来让展示元素尽可能在顶部。

对上述情况的调节代码如下:

// 若引导的元素不在页面范围内,则滚动页面到引导元素的视野范围内
adapteView(ele) {
    const rect = ele.getBoundingClientRect();
    const height = window.innerHeight;
    if (rect.top < 0 || rect.top + rect.height > height) {
        window.scrollBy(0, rect.top);
    }
}

接下来,我们就来一起实现下这个引导动画类。

第一步:实现Modal功能

我们先不管具体的展示逻辑实现,我们先实现一个简单的Modal功能。

class Guidences {
  constructor() {
    this.modal = null;
    this.eleList = [];
  }
  // 入口函数
  showGuidences(eleList = []) {
    // 允许传入单个元素
    this.eleList = eleList instanceof Array ? eleList : [eleList];
    // 若之前已经创建一个Modal实例,则不重复创建
    this.modal || this.createModel();
  }
  // 创建一个Modal实例
  createModel() {
    const modalContainer = document.createElement('div');
    const modalMask = document.createElement('div');
    this.setMaskStyle(modalMask);
    modalContainer.style.display = 'none';
    modalContainer.appendChild(modalMask);
    document.body.appendChild(modalContainer);
    this.modal = modalContainer;
  }

  setMaskStyle(ele) {
    ele.style.zIndex = '1000';
    ele.style.background = 'rgba(0, 0, 0, 0.8)';
    ele.style.position = 'fixed';
    ele.style.top = 0;
    ele.style.right = 0;
    ele.style.bottom = 0;
    ele.style.left = 0;
  }
 
  hideModal() {
    this.modal.style.display = 'none';
    this.modal.removeChild(this.modalBody);
    this.modalBody = null;
  }

  showModal() {
    this.modal.style.display = 'block';
  }
}

第二步:实现展示引导元素的功能

复制一个要展示元素的副本,并且根据要展示元素的位置信息来放置该副本,并且将副本当成Modal的主体内容展示。

class Guidences {
  constructor() {
    this.modal = null;
    this.eleList = [];
  }
  // 允许传入单个元素
  showGuidences(eleList = []) {
    this.eleList = eleList instanceof Array ? eleList : [eleList];
    this.modal || this.createModel();
    this.showGuidence();
  }
  // 展示引导页面
  showGuidence() {
    if (!this.eleList.length) {
      return this.hideModal();
    }
    this.modalBody && this.modal.removeChild(this.modalBody);
    const ele = this.eleList.shift(); // 当前要展示的元素
    const newEle = ele.cloneNode(true); // 复制副本
    this.modalBody = newEle;
    this.initModalBody(ele);
    this.showModal();
  }

  createModel() {
    // ...
  }

  setMaskStyle(ele) {
    // ...
  }

  initModalBody(target) {
    this.adapteView(target);
    const rect = target.getBoundingClientRect();
    this.modalBody.style.zIndex = '1001';
    this.modalBody.style.position = 'fixed';
    this.modalBody.style.width = `${rect.width}px`;
    this.modalBody.style.height = `${rect.height}px`;
    this.modalBody.style.left = `${rect.left}px`;
    this.modalBody.style.top = `${rect.top}px`;
    this.modal.appendChild(this.modalBody);
    // 当用户点击引导元素,则展示下一个要引导的元素
    this.modalBody.addEventListener('click', () => {
      this.showGuidence(this.eleList);
    });
  }
  // 若引导的元素不在页面范围内,则滚动页面到引导元素的视野范围内
  adapteView(ele) {
    const rect = ele.getBoundingClientRect();
    const height = window.innerHeight;
    if (rect.top < 0 || rect.top + rect.height > height) {
      window.scrollBy(0, rect.top);
    }
  }

  hideModal() {
      // ...
  }

  showModal() {
      // ...
  }
}

完整的代码可以在点击这里

调用方式

const guidences = new Guidences();
function showGuidences() {
    const eles = Array.from(document.querySelectorAll('.demo'));
    guidences.showGuidences(eles);
}
showGuidences();

总结

除了使用cloneNode的形式来实现引导动画外,还可以使用box-shadow、canvas等方式来做。详情可以看下这位老哥的文章新手引导动画的4种实现方式

本文地址在->本人博客地址, 欢迎给个 start 或 follow

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 第一,融资规模太大的不能碰 融资规模太大的项目不能碰。比如单个项目融资规模超过八个亿就算比较大。对于管理机构而言,...
    海持Alvin阅读 212评论 0 0
  • 今天在朋友圈偶然看见一个匿名留言板游戏的分享,心血来潮想玩一下。创建自己的匿名留言板,发了出去。 我自认平...
    红烛夜雨阅读 683评论 0 1
  • 湛蓝湛蓝,天际铺展 我的画布一一 完美无缺 羊群雪白游牧 太阳辉煌的帐篷,鼾声如雷 一只季节鸟 接受邀请 翅膀划响...
    忠志_3d7b阅读 115评论 1 4
  • Gunicorn是一个unix上被广泛使用的高性能的Python WSGI UNIX HTTP Server。和大...
    慢手暗夜阅读 163,292评论 2 29
  • 现金流量表:记录公司现金的流入流出情况 资产负债表、利润表、现金流量表关系密切 图片来自长投课程ppt 小熊定理四...
    粉百合阅读 850评论 0 4