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

前言

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

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

原理

  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获取的位置信息是相对于视口左上角位置的(如下图)。

对于第一种情况,这个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

原文地址:https://www.cnblogs.com/chenjg/p/9749802.html

时间: 2024-10-06 19:09:04

手把手教你实现一个引导动画的相关文章

手把手教你打造一个Material Design风格的App(一)

你应该听说过Android的Material Design,它是在Android 5.0(Lollipop)版本引入的.在Material Design中还引入了很多新东西,比如Material Theme,新的小部件,自定义的阴影,矢量图片及自定义动画等.如果你之前没有用过Material Design,那么本文将是一个很好的入门教程. 在这篇教程中,我们将会学习Material Design开发的基本步骤,即编写自定义的主题以及使用RecyclerView来实现抽屉导航. 通过下面的两个链接

手把手教你打造一个Material Design风格的App(二)

--接上文. 3.1添加ToolBar(ActionBar) 添加ToolBar非常简单,你需要做的仅仅是为toolbar创建一个单独的layout布局,如果你想在哪里展示toolbar,只要在对应布局里将toolbar的布局文件include进来即可. (8)在res-->layout文件夹下创建一个名为toolbar.xml的文件,然后在里面添加一个android.support.v7.widget.Toolbar元素,这样就创建了一个具有特定高度和主题的toolbar. toolbar.x

手把手教你打造一个Material Design风格的App(三)

--接上文. 3.2添加抽屉导航 添加导航抽屉跟Android 5.0之前是一样的,只是以前我们使用ListView来作为菜单容器,现在我们则使用Material Design风格的RecyclerView. (14)在你工程的java文件夹中,创建3个名为activity.adapter.model的包,将MainActivity.java移到activtiy包中,这样做使得你的代码可以很好地组织和管理. (15)打开位于app模块下的build.gradle文件并添加如下依赖.添加完依赖之后

手把手教你画一个 逼格满满圆形水波纹loadingview Android

才没有完结呢o( ̄︶ ̄)n .大家好,这里是番外篇. 拜读了爱哥的博客,又学到不少东西.爱哥曾经说过: 要站在巨人的丁丁上. 那么今天,我们就站在爱哥的丁丁上来学习制作一款自定义view(开个玩笑,爱哥看到别打我). 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50523713 上一篇 带领大家做了一款炫酷的loading动画view 手把手带你做一个超炫酷loading成功动画view  不知道大家跟着做了一遍没有呢? 在开始之

手把手教你做一个吸引人的购物网站

购物网站盈利能力相信很多用户都是有目共睹的,因此不少的中小企业对购物网站的建设也是趋之若鹜,怎么企业设计购物网站有什么方法能够为购物网站提高人气呢?下面看看凡科网站建设带来的一些分析. 要对用户的跟随心理进行分析.无论是实体销售还是线上的销售,用户都会有一种莫名的跟随心理.网上购物网站的评论就好想是生活中的口口相传,购买过的用户可以对产品进行评论,这样可以给潜在用户一个引导作用. 企业要对购物网站网页的每一个角落都要发挥极限.企业都知道网页的每一个角落都是有用的,购物网站也一样.一个列表页不会是

实用的viewpager 做一个引导动画(强调方法没有美化)

思路: 1.利用viewpager控件自带的水平滚动特性,将想要展现的内容用xml布局文件一张张写出来 2.用一个linearlayout包裹张数个imageview,src是一个小圆点,另外准备一个大圆点,给imageview定义一个索引值 3.首先进入动画时第一个原点是大的,进入第二张是将第一张的还原,第二章变大,这里我是用style样式setenabled方法来实现的 这个页面在应用还在加载时调用,负责判断是去引导页面还是直接去主页,如果首次进入或重新安装了此应用都将去往引导页面 <?xm

恭喜发财! -- 手把手教你仿造一个qq下拉抢红包 Android自定义view

猴年猴赛雷啊各位,今天没吃药我感觉自己萌萌哒! qq和微信和支付宝红包大战,不知道各位的战绩是多少嘞? 反正我qq抢到的都是气泡.因为太不爽,所以自己写一个下拉抢红包自己玩(自己跟自己玩). 先来看效果图.这个-- 呃~~ -__-" ..有点丑 是低仿. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50662592 学习完本篇博客你能获得到的知识 正确的获得view的大小 listview的下拉header 自定义字体 自己添加

手把手教你实现一个 Vue 进度条组件!

最近在个人的项目中,想对页面之间跳转的过程进行优化,想到了很多文档或 npm 等都用到的页面跳转进度条,于是便想自己去实现一个,特此记录. 来看下 npm 搜索组件时候的效果: so 下面咱们一起动手实现一下呗.  定义使用方式  想实现一个组件的前提,一定要想好你的需求是什么,还要自己去定义一个舒服的使用方法,这其中也是有原则的,对使用者来说,使用方式越简单越好.那么对应的代价就是写这个组件的复杂度会变高. 我想要的使用方式是这样的:可以在任意的地方去调用到这个方法,可以随时控制其状态.  看

手把手教你构建一个音视频小程序

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯视频云终端团队发表于云+社区专栏 腾讯云提供了全套技术文档和源码来帮助您快速构建一个音视频小程序,但是再好的源码和文档也有学习成本,为了尽快的能调试起来,我们还提供了一个免费的一键部署服务:您只需轻点几下鼠标,就可以在自己的账号下获得一个音视频小程序,同时附送一台拥有独立域名的测试服务器,让您可以在 5 分钟内快速构建出自己的测试环境. 通过微信公众平台授权登录腾讯云 打开 微信公众平台 注册并登录小程序,按如下步骤操作: 单