1. 固定步长的动画

  1. // JS动画的原理:通过定时器去不断修改一个元素的某个属性,因为定时器每隔一段时间就会执行一次,元素的属性每隔一段时间就会被修改一次,元素就去了新的位置,人的眼睛看到的就是动画效果;
  2. // 固定步长的动画:每次定时器执行让这个元素改变固定的步长。
  3. const { win, css } = window.utils;
  4. let box = document.getElementById('box');
  5. let step = 10; // 设置步长
  6. let maxL = win('clientWidth') - css(box, 'width');
  7. let timer = setInterval(() => {
  8. // 固定步长的动画,在原有的基础上累加上步长,然后再设置回去
  9. let preLeft = css(box, 'left'); // 获取原有的 left 值
  10. let curLeft = preLeft + step;
  11. // 动画过界判断: 判断元素是否运动到边界了,如果到达超过边界,先把定时器清除了,停止动画,并且把 curLeft 改成最大值
  12. if (curLeft >= maxL) {
  13. clearInterval(timer);
  14. curLeft = maxL;
  15. }
  16. css(box, 'left', curLeft); // curLeft 是当前元素因下一帧的位置,要把 curLeft 设置给 box 的
  17. // left 属性,box 才会去 curLeft 的位置
  18. }, 16);

2. 指定时长的动画

  1. /*
  2. * 目标:
  3. * 1. 理解指定时长的动画原理
  4. * 2. 比较指定时长和指定步长动画的区别
  5. *
  6. * */
  7. // 固定时长的动画:在指定的时间内,从某个位置运动到指定的位置;
  8. // 在 3s 内运动到 left 的值是800;
  9. const {css, win} = window.utils;
  10. let time = 3000; // 3s 指定时间 3s
  11. let target = 800; // 目标位置
  12. // 路程 = 目标位置 - 起点位置;
  13. let begin = css(box, 'left');
  14. let change = target - begin; // 路程
  15. // 速度 = 路程 / 时间
  16. let speed = change / time; // 速度
  17. // console.log(speed);
  18. let curT = 0; // 记录从动画开始后经过的时间
  19. // 使用定时器开启动画
  20. let timer = setInterval(() => {
  21. curT += 16; // curT经过的时间
  22. console.log(curT);
  23. if (curT >= time) { // 动画过界判断:如果 curT 大于3000,就应该修正成3000
  24. clearInterval(timer);
  25. curT = time;
  26. }
  27. let curLeft = curT * speed; // 在 curT 时间内走过的路程
  28. let total = begin + curLeft; // 经过 curT 时间后,元素应该所处的位置
  29. css(box, 'left', total);
  30. }, 16);

3. utils库

  1. // 封装一个工具集:增强代码的可复用性,提升开发效率;
  2. // utils 工具包,这里面提供了常用的方法;
  3. window.utils = (function () {
  4. /**
  5. * @desc 类数组转数组
  6. * @param arrLike 类数组对象
  7. * @returns {Array} 类数组对象转成的数组
  8. */
  9. function arrLikeToAry(arrLike) {
  10. try {
  11. return Array.from(arrLike);
  12. } catch (e) {
  13. var ary = []; //
  14. for (var i = 0; i < arrLike.length; i++) {
  15. ary.push(arrLike[i]);
  16. }
  17. return ary;
  18. }
  19. }
  20. /**
  21. * @desc JSON格式字符串转对象
  22. * @param jsonstr JSON格式字符串
  23. * @returns {Object} 对象
  24. */
  25. function toJSON(jsonstr) {
  26. if ('JSON' in window) { // 'JSON' in window 返回 false 表示 JSON 的方法不可以用
  27. return JSON.parse(jsonstr);
  28. } else {
  29. return eval('(' + jsonstr + ')');
  30. }
  31. }
  32. /**
  33. * @desc 获取 documentElement、document.body 的盒子模型属性
  34. * @param attr 盒子模型属性名
  35. * @param val 设置的值
  36. * @returns {*} 获取的盒子模型属性值
  37. */
  38. function win(attr, val) {
  39. if (typeof val === 'undefined') {
  40. // 如果 val 是 undefined,证明第二个参数没传,没传就是获取
  41. return document.documentElement[attr] || document.body[attr] // 如果函数法返回值是表达
  42. 式,它会等着表达式求值,把求出来的值作为返回值返回
  43. }
  44. document.documentElement[attr] = document.body[attr] = val;
  45. }
  46. /**
  47. * @desc 获取当前元素相对于 body 的左上角点坐标()
  48. * @param ele 当前元素
  49. * @returns {{left: number, top: number}} left:元素左外边到 body 左内边的距离; top: 元素的上
  50. 外边距离 body 上内边的距离
  51. */
  52. function offset(ele) {
  53. let left = ele.offsetLeft; // 当前元素的 offsetLeft
  54. let top = ele.offsetTop; // 当前元素的 offsetTop
  55. let parent = ele.offsetParent; // 获取当前元素的 offsetParent
  56. while (parent && parent.nodeName !== 'BODY') {
  57. left += parent.clientLeft + parent.offsetLeft;
  58. top += parent.clientTop + parent.offsetTop;
  59. parent = parent.offsetParent;
  60. }
  61. return {
  62. left,
  63. top
  64. }
  65. }
  66. /**
  67. * @desc 获取元素的计算生效的样式值
  68. * @param ele 元素对象
  69. * @param attr css属性
  70. * @returns {*} css样式计算生效后的值
  71. */
  72. function getCss(ele, attr) {
  73. var value;
  74. // 1. 判断是否是 IE 浏览器
  75. if ('getComputedStyle' in window) { // 判断 window 对象上有 getComputedStyle 吗
  76. value = window.getComputedStyle(ele, null)[attr];
  77. } else {
  78. // 执行 else 的时候说明是 IE 低版本,使用 currentStyle 属性
  79. value = ele.currentStyle[attr];
  80. }
  81. // 把单位去掉:把数字且带单位的,把单位去掉
  82. var reg = /^-?\d+(\.\d+)?px|rem|em|pt$/g;
  83. if (reg.test(value)) {
  84. value = parseFloat(value);
  85. }
  86. return value
  87. }
  88. /**
  89. * @desc 设置元素对象的样式
  90. * @param ele 元素对象
  91. * @param attr CSS属性
  92. * @param val 样式的值
  93. */
  94. function setCss(ele, attr, val) {
  95. let reg = /^(fontSize|width|height|(margin|padding)?(top|right|bottom|left)?)$/i;
  96. if (reg.test(attr)) {
  97. if (!isNaN(val)) val += 'px';
  98. }
  99. ele.style[attr] = val;
  100. }
  101. /**
  102. * @desc 批量设置CSS样式
  103. * @param ele
  104. * @param cssBatch
  105. */
  106. function setBatchCss(ele, cssBatch) {
  107. // 批量设置 css 样式就是遍历传入的 CSS 对象,把样式和值依次设置给元素对象即可
  108. // 检验 cssBatch 是不是一个对象
  109. if (typeof cssBatch !== 'object') {
  110. throw TypeError('cssBatch is not a object')
  111. }
  112. for (let key in cssBatch) {
  113. // hasOwnProperty() 检测某个属性是不是对象私有的,是则 true,不是返回 false
  114. if (cssBatch.hasOwnProperty(key)) { // 去复习for in循环、面向对象
  115. setCss(ele, key, cssBatch[key]);
  116. }
  117. }
  118. }
  119. /**
  120. * @desc 封装一个 css 的方法,根据参数不同有不同的功能
  121. * @param ele 元素
  122. * @param param CSS 样式或者 CSS 样式对象
  123. * @param val CSS 样式值
  124. * @returns {*} 获取时是 CSS 样式值
  125. */
  126. function css(ele, param, val) {
  127. // 根据传参的不同调用不同的方法
  128. // 第二个参数是字符串类型,不传 val 时,就是获取
  129. // 第二个参数是字符串时,并且第三个参数传了,就是设置单个样式
  130. // 第二个参数是对象时,就是批量设置 CSS 样式
  131. if (typeof param === 'string' && typeof val === 'undefined') {
  132. return getCss(ele, param);
  133. }
  134. if (typeof param === 'string' && typeof val !== 'undefined') {
  135. setCss(ele, param, val);
  136. return;
  137. }
  138. if (typeof param === 'object') {
  139. setBatchCss(ele, param);
  140. }
  141. }
  142. /**
  143. * @desc 判断当前元素有没有某个类名
  144. * @param ele 元素对象
  145. * @param className 类名
  146. * @returns {boolean} 是否有类名
  147. */
  148. function hasClass(ele, className) {
  149. let cN = className.trim();
  150. // return ele.className.includes(cN);
  151. return ele.className.indexOf(cN) !== -1;
  152. }
  153. /**
  154. * @desc 为元素添加类名
  155. * @param ele 元素对象
  156. * @param className 类名
  157. */
  158. function addClass(ele, className) {
  159. let cN = className.trim();
  160. if (hasClass(ele, cN)) return;
  161. // 优化:如果原来的类名末尾有空格,就可以不拼接空格,如果没有时再拼接
  162. let reg = / $/g;
  163. // 'box '
  164. if (reg.test(ele.className)) {
  165. ele.className += `${cN}`;
  166. } else {
  167. ele.className += ` ${cN}`;
  168. }
  169. }
  170. /**
  171. * @desc 移除类名
  172. * @param ele 元素对象
  173. * @param className 类名
  174. */
  175. function removeClass(ele, className) {
  176. let cN = className.trim();
  177. let reg = new RegExp(cN, 'g');
  178. ele.className = ele.className.replace(reg, '');
  179. }
  180. return {
  181. arrLikeToAry,
  182. toJSON,
  183. win,
  184. offset,
  185. getCss,
  186. setCss,
  187. setBatchCss,
  188. css,
  189. hasClass,
  190. addClass,
  191. removeClass
  192. }
  193. })();

案例:回到顶部

css部分

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport"
  6. content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <title>Document</title>
  9. <style>
  10. * {
  11. margin: 0;
  12. padding: 0;
  13. }
  14. .box {
  15. height: 5000px;
  16. background: -webkit-linear-gradient(top left, red, blue, green, orange);
  17. }
  18. .btn {
  19. position: fixed;
  20. right: 50px;
  21. bottom: 50px;
  22. border-radius: 50%;
  23. width: 80px;
  24. height: 80px;
  25. background: #999;
  26. line-height: 80px;
  27. text-align: center;
  28. color: #fff;
  29. -webkit-user-select: none;
  30. user-select: none;
  31. cursor: pointer;
  32. text-decoration: none;
  33. }
  34. </style>
  35. </head>
  36. <body>
  37. <div id="box" class="box"></div>
  38. <div class="btn" id="btn">回到顶部</div>
  39. <!--<a href="#box" class="btn">回到顶部</a>-->
  40. <!--锚点定位:把 a 标签的 href 设置成某个元素的 id# 元素 id 点击 a 标签就可以回到顶部 -->
  41. <script src="js/utils.js"></script>
  42. <script src="js/4-回到顶部.js"></script>
  43. </body>
  44. </html>

js部分

  1. const {win} = window.utils; // 从 utils 中解构 win 方法
  2. // 写一个动画,让页面缓慢的回到顶部
  3. // 让页面回到顶部:需要操作盒子模型的 scrollTop 属性(页面滚动条卷去的距离)
  4. let winScrollTop = win('scrollTop'); // win 方法获取页面滚动条卷去的高度;
  5. let btn = document.getElementById('btn');
  6. // 点击的时候才会触发滚动的行为
  7. // 1. 瞬间回去
  8. /*
  9. btn.onclick = function () {
  10. win('scrollTop', 0);
  11. };
  12. */
  13. // 2. 动画着回去:用一个定时器不断的修改页面的 scrollTop 值
  14. /*let time = 2000; // 指定时间回去; 需要计算速度
  15. let isRun = false; // 标识符:标识当前的滚动条是否正在运动
  16. btn.onclick = function () {
  17. if (isRun) return; // 如果 isRun 是 true ,说明现在有正在执行的动画,为了避免动画累加,后面的代
  18. 码不能执行;
  19. // 1. 计算速度
  20. let winScrollTop = win('scrollTop'); // 点击时页面滚动条卷去的高度
  21. let speed = winScrollTop / time; // 计算速度
  22. let curT = 0; // curT 记录经过的时间
  23. isRun = true; // 下一行就要开启定时器了,所以在这里把 isRun 置为 true
  24. let timer = setInterval(() => {
  25. curT += 16; // 让时间累加
  26. if (curT >= time) { // 当到大于等于 time 时,应该滚动到底了
  27. clearInterval(timer);
  28. curT = time;
  29. isRun = false; // 动画结束后把 isRun 置为 false
  30. }
  31. let change = 16 * speed; // 在 curT 时间内走过的路程
  32. winScrollTop -= change; // 经过 curT 时间后,页面滚动条的位置
  33. win('scrollTop', winScrollTop); // 设置回去
  34. }, 16);
  35. };*/
  36. // 避免动画累加的第一种方案:设置标识符,初始值是 false,表示当前没有正在执行的动画。如果开始动画,我们就把这个标识符置为 true;当我们再次开启动画之前,如果这个标识符是 true,表示当前有正在执行到的动画,就不能再次开启相同的动画;而且注意当动画结束后,把标识符修改为 false;
  37. // 第二种解决动画累加的解决方案:在开启新的动画之前把之前的动画清除掉(把之前的定时器清除了);
  38. let time = 2000;
  39. // let timerId = null; 一般我们不用全局变量记录定时器 id,一般使用自定义属性的方式来记录定时器的 ID;
  40. btn.onclick = function () {
  41. // 1. 计算速度
  42. let winScrollTop = win('scrollTop'); // 点击时页面滚动条卷去的高度
  43. if (winScrollTop <= 0) return; // 如果卷去的高度为小于等于0,不开启动画
  44. let speed = winScrollTop / time; // 计算速度
  45. let curT = 0; // curT 记录经过的时间
  46. if (this.timerId) {
  47. // 如果 timerId 不是 null 就说明之前已经有动画了,在开启新的动画之前把原来的动画清除掉
  48. clearInterval(this.timerId);
  49. }
  50. this.timerId = setInterval(() => {
  51. curT += 16; // 让时间累加
  52. if (curT >= time) { // 当到大于等于 time 时,应该滚动到底了
  53. clearInterval(this.timerId);
  54. curT = time;
  55. }
  56. let change = 16 * speed; // 在 curT 时间内走过的路程
  57. winScrollTop -= change; // 经过 curT 时间后,页面滚动条的位置
  58. win('scrollTop', winScrollTop); // 设置回去
  59. }, 16);
  60. };