引言

       平常会做一些有意思的小案例练手,通常都会发到codepen上,但是codepen不能写分析。

       所以就在博客上开个案例分享系列,对demo做个剖析。目的以分享为主,然后也希望各路大神能给出改进的想法,在review中提升技术,发现乐趣~

1、效果图

完整效果,请移步 codepen-流星雨案例

纯CSS案例及本篇案例完整源码,请移步The night of the metero

2、源码

  • HTML
  1. <body>
  2. <div class="container">
  3. <div id="mask"></div>
  4. <div id="sky"></div>
  5. <div id="moon"></div>
  6. <div id="stars"></div>
  7. <div class="cloud cloud-1"></div>
  8. <div class="cloud cloud-2"></div>
  9. <div class="cloud cloud-3"></div>
  10. </div>
  11. </body>
  • CSS
  1. /* ------------reset------------ */
  2. * {
  3. margin: 0;
  4. padding: 0;
  5. }
  6. html,
  7. body {
  8. width: 100%;
  9. min-width: 1000px;
  10. height: 100%;
  11. min-height: 400px;
  12. overflow: hidden;
  13. }
  14. /* ------------画布 ------------ */
  15. .container {
  16. position: relative;
  17. height: 100%;
  18. }
  19. /* 遮罩层 */
  20. #mask {
  21. position: absolute;
  22. width: 100%;
  23. height: 100%;
  24. background: rgba(0, 0, 0, .8);
  25. z-index: 900;
  26. }
  27. /* 天空背景 */
  28. #sky {
  29. width: 100%;
  30. height: 100%;
  31. background: linear-gradient(rgba(0, 150, 255, 1), rgba(0, 150, 255, .8), rgba(0, 150, 255, .5));
  32. }
  33. /* 月亮 */
  34. #moon {
  35. position: absolute;
  36. top: 50px;
  37. right: 200px;
  38. width: 120px;
  39. height: 120px;
  40. background: rgba(251, 255, 25, 0.938);
  41. border-radius: 50%;
  42. box-shadow: 0 0 20px rgba(251, 255, 25, 0.5);
  43. z-index: 9999;
  44. }
  45. /* 闪烁星星 */
  46. .blink {
  47. position: absolute;
  48. background: rgb(255, 255, 255);
  49. border-radius: 50%;
  50. box-shadow: 0 0 5px rgb(255, 255, 255);
  51. opacity: 0;
  52. z-index: 10000;
  53. }
  54. /* 流星 */
  55. .star {
  56. position: absolute;
  57. opacity: 0;
  58. z-index: 10000;
  59. }
  60. .star::after {
  61. content: "";
  62. display: block;
  63. border: solid;
  64. border-width: 2px 0 2px 80px;
  65. /*流星随长度逐渐缩小*/
  66. border-color: transparent transparent transparent rgba(255, 255, 255, 1);
  67. border-radius: 2px 0 0 2px;
  68. transform: rotate(-45deg);
  69. transform-origin: 0 0 0;
  70. box-shadow: 0 0 20px rgba(255, 255, 255, .3);
  71. }
  72. /* 云 */
  73. .cloud {
  74. position: absolute;
  75. width: 100%;
  76. height: 100px;
  77. }
  78. .cloud-1 {
  79. bottom: -100px;
  80. z-index: 1000;
  81. opacity: 1;
  82. transform: scale(1.5);
  83. -webkit-transform: scale(1.5);
  84. -moz-transform: scale(1.5);
  85. -ms-transform: scale(1.5);
  86. -o-transform: scale(1.5);
  87. }
  88. .cloud-2 {
  89. left: -100px;
  90. bottom: -50px;
  91. z-index: 999;
  92. opacity: .5;
  93. transform: rotate(7deg);
  94. -webkit-transform: rotate(7deg);
  95. -moz-transform: rotate(7deg);
  96. -ms-transform: rotate(7deg);
  97. -o-transform: rotate(7deg);
  98. }
  99. .cloud-3 {
  100. left: 120px;
  101. bottom: -50px;
  102. z-index: 999;
  103. opacity: .1;
  104. transform: rotate(-10deg);
  105. -webkit-transform: rotate(-10deg);
  106. -moz-transform: rotate(-10deg);
  107. -ms-transform: rotate(-10deg);
  108. -o-transform: rotate(-10deg);
  109. }
  110. .circle {
  111. position: absolute;
  112. border-radius: 50%;
  113. background: #fff;
  114. }
  115. .circle-1 {
  116. width: 100px;
  117. height: 100px;
  118. top: -50px;
  119. left: 10px;
  120. }
  121. .circle-2 {
  122. width: 150px;
  123. height: 150px;
  124. top: -50px;
  125. left: 30px;
  126. }
  127. .circle-3 {
  128. width: 300px;
  129. height: 300px;
  130. top: -100px;
  131. left: 80px;
  132. }
  133. .circle-4 {
  134. width: 200px;
  135. height: 200px;
  136. top: -60px;
  137. left: 300px;
  138. }
  139. .circle-5 {
  140. width: 80px;
  141. height: 80px;
  142. top: -30px;
  143. left: 450px;
  144. }
  145. .circle-6 {
  146. width: 200px;
  147. height: 200px;
  148. top: -50px;
  149. left: 500px;
  150. }
  151. .circle-7 {
  152. width: 100px;
  153. height: 100px;
  154. top: -10px;
  155. left: 650px;
  156. }
  157. .circle-8 {
  158. width: 50px;
  159. height: 50px;
  160. top: 30px;
  161. left: 730px;
  162. }
  163. .circle-9 {
  164. width: 100px;
  165. height: 100px;
  166. top: 30px;
  167. left: 750px;
  168. }
  169. .circle-10 {
  170. width: 150px;
  171. height: 150px;
  172. top: 10px;
  173. left: 800px;
  174. }
  175. .circle-11 {
  176. width: 150px;
  177. height: 150px;
  178. top: -30px;
  179. left: 850px;
  180. }
  181. .circle-12 {
  182. width: 250px;
  183. height: 250px;
  184. top: -50px;
  185. left: 900px;
  186. }
  187. .circle-13 {
  188. width: 200px;
  189. height: 200px;
  190. top: -40px;
  191. left: 1000px;
  192. }
  193. .circle-14 {
  194. width: 300px;
  195. height: 300px;
  196. top: -70px;
  197. left: 1100px;
  198. }
  • JS
  1. //流星动画
  2. setInterval(function() {
  3. const obj = addChild("#sky", "div", 2, "star");
  4. for (let i = 0; i < obj.children.length; i++) {
  5. const top = -50 + Math.random() * 200 + "px",
  6. left = 200 + Math.random() * 1200 + "px",
  7. scale = 0.3 + Math.random() * 0.5;
  8. const timer = 1000 + Math.random() * 1000;
  9. obj.children[i].style.top = top;
  10. obj.children[i].style.left = left;
  11. obj.children[i].style.transform = `scale(${scale})`;
  12. requestAnimation({
  13. ele: obj.children[i],
  14. attr: ["top", "left", "opacity"],
  15. value: [150, -150, .8],
  16. time: timer,
  17. flag: false,
  18. fn: function() {
  19. requestAnimation({
  20. ele: obj.children[i],
  21. attr: ["top", "left", "opacity"],
  22. value: [150, -150, 0],
  23. time: timer,
  24. flag: false,
  25. fn: () => {
  26. obj.parent.removeChild(obj.children[i]);
  27. }
  28. })
  29. }
  30. });
  31. }
  32. }, 1000);
  33. //闪烁星星动画
  34. setInterval(function() {
  35. const obj = addChild("#stars", "div", 2, "blink");
  36. for (let i = 0; i < obj.children.length; i++) {
  37. const top = -50 + Math.random() * 500 + "px",
  38. left = 200 + Math.random() * 1200 + "px",
  39. round = 1 + Math.random() * 2 + "px";
  40. const timer = 1000 + Math.random() * 4000;
  41. obj.children[i].style.top = top;
  42. obj.children[i].style.left = left;
  43. obj.children[i].style.width = round;
  44. obj.children[i].style.height = round;
  45. requestAnimation({
  46. ele: obj.children[i],
  47. attr: "opacity",
  48. value: .5,
  49. time: timer,
  50. flag: false,
  51. fn: function() {
  52. requestAnimation({
  53. ele: obj.children[i],
  54. attr: "opacity",
  55. value: 0,
  56. time: timer,
  57. flag: false,
  58. fn: function() {
  59. obj.parent.removeChild(obj.children[i]);
  60. }
  61. });
  62. }
  63. });
  64. }
  65. }, 1000);
  66. //月亮移动
  67. requestAnimation({
  68. ele: "#moon",
  69. attr: "right",
  70. value: 1200,
  71. time: 10000000,
  72. });
  73. // 添加云
  74. const clouds = addChild(".cloud", "div", 14, "circle", true);
  75. for (let i = 0; i < clouds.children.length; i++) {
  76. for (let j = 0; j < clouds.children[i].length;) {
  77. clouds.children[i][j].classList.add(`circle-${++j}`);
  78. }
  79. }
  80. //云动画
  81. let flag = 1;
  82. setInterval(
  83. function() {
  84. const clouds = document.querySelectorAll(".cloud");
  85. const left = Math.random() * 5;
  86. bottom = Math.random() * 5;
  87. let timer = 0;
  88. for (let i = 0; i < clouds.length; i++) {
  89. requestAnimation({
  90. ele: clouds[i],
  91. attr: ["left", "bottom"],
  92. value: flag % 2 ? [-left, -bottom] : [left, bottom],
  93. time: timer += 500,
  94. flag: false,
  95. fn: function() {
  96. requestAnimation({
  97. ele: clouds[i],
  98. attr: ["left", "bottom"],
  99. value: flag % 2 ? [left, bottom] : [-left, -bottom],
  100. time: timer,
  101. flag: false
  102. })
  103. }
  104. });
  105. }
  106. flag++;
  107. }, 2000)
  • 封装方法
  1. //———————————————————————————————————————————动画———————————————————————————————————————————————————
  2. //运动动画,调用Tween.js
  3. //ele: dom | class | id | tag 节点 | 类名 | id名 | 标签名,只支持选择一个节点,class类名以及标签名只能选择页面中第一个
  4. //attr: attribute 属性名
  5. //value: target value 目标值
  6. //time: duration 持续时间
  7. //tween: timing function 函数方程
  8. //flag: Boolean 判断是按值移动还是按位置移动,默认按位置移动
  9. //fn: callback 回调函数
  10. //增加返回值: 将内部参数对象返回,可以通过设置返回对象的属性stop为true打断动画
  11. function requestAnimation(obj) {
  12. //—————————————————————————————————————参数设置—————————————————————————————————————————————
  13. //默认属性
  14. const parameter = {
  15. ele: null,
  16. attr: null,
  17. value: null,
  18. time: 1000,
  19. tween: "linear",
  20. flag: true,
  21. stop: false,
  22. fn: ""
  23. }
  24. //合并传入属性
  25. Object.assign(parameter, obj); //覆盖重名属性
  26. //—————————————————————————————————————动画设置—————————————————————————————————————————————
  27. //创建运动方程初始参数,方便复用
  28. let start = 0; //用于保存初始时间戳
  29. let target = (typeof parameter.ele === "string" ? document.querySelector(parameter.ele) : parameter.ele), //目标节点
  30. attr = parameter.attr, //目标属性
  31. beginAttr = parseFloat(getComputedStyle(target)[attr]), //attr起始值
  32. value = parameter.value, //运动目标值
  33. count = value - beginAttr, //实际运动值
  34. time = parameter.time, //运动持续时间,
  35. tween = parameter.tween, //运动函数
  36. flag = parameter.flag,
  37. callback = parameter.fn, //回调函数
  38. curVal = 0; //运动当前值
  39. //判断传入函数是否为数组,多段运动
  40. (function() {
  41. if (attr instanceof Array) {
  42. beginAttr = [];
  43. count = [];
  44. for (let i of attr) {
  45. const val = parseFloat(getComputedStyle(target)[i]);
  46. beginAttr.push(val);
  47. count.push(value - val);
  48. }
  49. }
  50. if (value instanceof Array) {
  51. for (let i in value) {
  52. count[i] = value[i] - beginAttr[i];
  53. }
  54. }
  55. })();
  56. //运动函数
  57. function animate(timestamp) {
  58. if (parameter.stop) return; //打断
  59. //存储初始时间戳
  60. if (!start) start = timestamp;
  61. //已运动时间
  62. let t = timestamp - start;
  63. //判断多段运动
  64. if (beginAttr instanceof Array) {
  65. // const len = beginAttr.length //存数组长度,复用
  66. //多段运动第1类——多属性,同目标,同时间/不同时间
  67. if (typeof count === "number") { //同目标
  68. //同时间
  69. if (typeof time === "number") {
  70. if (t > time) t = time; //判断是否超出目标值
  71. //循环attr,分别赋值
  72. for (let i in beginAttr) {
  73. if (flag) curVal = Tween[tween](t, beginAttr[i], count, time); //调用Tween,返回当前属性值,此时计算方法为移动到写入位置
  74. else curVal = Tween[tween](t, beginAttr[i], count + beginAttr[i], time); //调用Tween,返回当前属性值,此时计算方法为移动了写入距离
  75. if (attr[i] === "opacity") target.style[attr[i]] = curVal; //给属性赋值
  76. else target.style[attr[i]] = curVal + "px"; //给属性赋值
  77. if (t < time) requestAnimationFrame(animate); //判断是否运动完
  78. else callback && callback(); //调用回调函数
  79. }
  80. return;
  81. }
  82. //不同时间
  83. if (time instanceof Array) {
  84. //循环time,attr,分别赋值
  85. for (let i in beginAttr) {
  86. //错误判断
  87. if (!time[i] && time[i] !== 0) {
  88. throw new Error(
  89. "The input time's length is not equal to attribute's length");
  90. }
  91. //判断是否已经完成动画,完成则跳过此次循环
  92. if (parseFloat(getComputedStyle(target)[attr[i]]) === (typeof value === "number" ? value : value[i]))
  93. continue;
  94. // t = timestamp - start; //每次循环初始化t
  95. if (t > time[i]) t = time[i]; //判断是否超出目标值
  96. if (flag || attr[i] === "opacity") curVal = Tween[tween](t, beginAttr[i], count, i); //调用Tween,返回当前属性值,此时计算方法为移动到写入位置
  97. else curVal = Tween[tween](t, beginAttr[i], count + beginAttr[i], i); //调用Tween,返回当前属性值,此时计算方法为移动了写入距离
  98. if (attr[i] === "opacity") target.style[attr[i]] = curVal; //给属性赋值
  99. else target.style[attr[i]] = curVal + "px"; //给属性赋值
  100. }
  101. if (t < Math.max(...time)) requestAnimationFrame(animate); //判断函数是否运动完
  102. else callback && callback(); //如果已经执行完时间最长的动画,则调用回调函数
  103. return;
  104. }
  105. }
  106. //多段运动第2类——多属性,不同目标,同时间/不同时间
  107. if (count instanceof Array) {
  108. //同时间
  109. if (typeof time === "number") {
  110. if (t > time) t = time; //判断是否超出目标值
  111. for (let i in beginAttr) { //循环attr,count,分别赋值
  112. //错误判断
  113. if (!count[i] && count[i] !== 0) {
  114. throw new Error(
  115. "The input value's length is not equal to attribute's length");
  116. }
  117. if (flag || attr[i] === "opacity") curVal = Tween[tween](t, beginAttr[i], count[i], time); //调用Tween,返回当前属性值,此时计算方法为移动到写入位置
  118. else curVal = Tween[tween](t, beginAttr[i], count[i] + beginAttr[i], time); //调用Tween,返回当前属性值,此时计算方法为移动了写入距离
  119. if (attr[i] === "opacity") target.style[attr[i]] = curVal; //给属性赋值
  120. else target.style[attr[i]] = curVal + "px"; //给属性赋值
  121. }
  122. if (t < time) requestAnimationFrame(animate); //判断函数是否运动完
  123. else callback && callback(); //如果已经执行完时间最长的动画,则调用回调函数
  124. return;
  125. }
  126. //不同时间
  127. if (time instanceof Array) {
  128. for (let i in beginAttr) {
  129. //错误判断
  130. if (!time[i] && time[i] !== 0) {
  131. throw new Error(
  132. "The input time's length is not equal to attribute's length");
  133. }
  134. //判断是否已经完成动画,完成则跳过此次循环
  135. if (parseFloat(getComputedStyle(target)[attr[i]]) === (typeof value === "number" ? value : value[i]))
  136. continue;
  137. if (t > time[i]) t = time[i]; //判断是否超出目标值
  138. //错误判断
  139. if (!count[i] && count[i] !== 0) {
  140. throw new Error(
  141. "The input value's length is not equal to attribute's length");
  142. }
  143. if (flag || attr[i] === "opacity") curVal = Tween[tween](t, beginAttr[i], count[i], time[i]); //调用Tween,返回当前属性值,此时计算方法为移动到写入位置
  144. else curVal = Tween[tween](t, beginAttr[i], count[i] + beginAttr[i], time[i]); //调用Tween,返回当前属性值,此时计算方法为移动了写入距离
  145. if (attr[i] === "opacity") target.style[attr[i]] = curVal;
  146. else target.style[attr[i]] = curVal + "px";
  147. }
  148. if (t < Math.max(...time)) requestAnimationFrame(animate);
  149. else callback && callback();
  150. return;
  151. }
  152. }
  153. }
  154. //单运动模式
  155. if (t > time) t = time;
  156. if (flag || attr === "opacity") curVal = Tween[tween](t, beginAttr, count, time); //调用Tween,返回当前属性值,此时计算方法为移动到写入位置
  157. else curVal = Tween[tween](t, beginAttr[i], count + beginAttr, time); //调用Tween,返回当前属性值,此时计算方法为移动了写入距离
  158. if (attr === "opacity") target.style[attr] = curVal;
  159. else target.style[attr] = curVal + "px";
  160. if (t < time) requestAnimationFrame(animate);
  161. else callback && callback();
  162. }
  163. requestAnimationFrame(animate);
  164. return parameter; //返回对象,供打断或其他用途
  165. }
  166. //Tween.js
  167. /*
  168. * t : time 已过时间
  169. * b : begin 起始值
  170. * c : count 总的运动值
  171. * d : duration 持续时间
  172. *
  173. * 曲线方程
  174. *
  175. * http://www.cnblogs.com/bluedream2009/archive/2010/06/19/1760909.html
  176. * */
  177. let Tween = {
  178. linear: function(t, b, c, d) { //匀速
  179. return c * t / d + b;
  180. },
  181. easeIn: function(t, b, c, d) { //加速曲线
  182. return c * (t /= d) * t + b;
  183. },
  184. easeOut: function(t, b, c, d) { //减速曲线
  185. return -c * (t /= d) * (t - 2) + b;
  186. },
  187. easeBoth: function(t, b, c, d) { //加速减速曲线
  188. if ((t /= d / 2) < 1) {
  189. return c / 2 * t * t + b;
  190. }
  191. return -c / 2 * ((--t) * (t - 2) - 1) + b;
  192. },
  193. easeInStrong: function(t, b, c, d) { //加加速曲线
  194. return c * (t /= d) * t * t * t + b;
  195. },
  196. easeOutStrong: function(t, b, c, d) { //减减速曲线
  197. return -c * ((t = t / d - 1) * t * t * t - 1) + b;
  198. },
  199. easeBothStrong: function(t, b, c, d) { //加加速减减速曲线
  200. if ((t /= d / 2) < 1) {
  201. return c / 2 * t * t * t * t + b;
  202. }
  203. return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
  204. },
  205. elasticIn: function(t, b, c, d, a, p) { //正弦衰减曲线(弹动渐入)
  206. if (t === 0) {
  207. return b;
  208. }
  209. if ((t /= d) == 1) {
  210. return b + c;
  211. }
  212. if (!p) {
  213. p = d * 0.3;
  214. }
  215. if (!a || a < Math.abs(c)) {
  216. a = c;
  217. var s = p / 4;
  218. } else {
  219. var s = p / (2 * Math.PI) * Math.asin(c / a);
  220. }
  221. return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
  222. },
  223. elasticOut: function(t, b, c, d, a, p) { //正弦增强曲线(弹动渐出)
  224. if (t === 0) {
  225. return b;
  226. }
  227. if ((t /= d) == 1) {
  228. return b + c;
  229. }
  230. if (!p) {
  231. p = d * 0.3;
  232. }
  233. if (!a || a < Math.abs(c)) {
  234. a = c;
  235. var s = p / 4;
  236. } else {
  237. var s = p / (2 * Math.PI) * Math.asin(c / a);
  238. }
  239. return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
  240. },
  241. elasticBoth: function(t, b, c, d, a, p) {
  242. if (t === 0) {
  243. return b;
  244. }
  245. if ((t /= d / 2) == 2) {
  246. return b + c;
  247. }
  248. if (!p) {
  249. p = d * (0.3 * 1.5);
  250. }
  251. if (!a || a < Math.abs(c)) {
  252. a = c;
  253. var s = p / 4;
  254. } else {
  255. var s = p / (2 * Math.PI) * Math.asin(c / a);
  256. }
  257. if (t < 1) {
  258. return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) *
  259. Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
  260. }
  261. return a * Math.pow(2, -10 * (t -= 1)) *
  262. Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
  263. },
  264. backIn: function(t, b, c, d, s) { //回退加速(回退渐入)
  265. if (typeof s == 'undefined') {
  266. s = 1.70158;
  267. }
  268. return c * (t /= d) * t * ((s + 1) * t - s) + b;
  269. },
  270. backOut: function(t, b, c, d, s) {
  271. if (typeof s == 'undefined') {
  272. s = 3.70158; //回缩的距离
  273. }
  274. return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
  275. },
  276. backBoth: function(t, b, c, d, s) {
  277. if (typeof s == 'undefined') {
  278. s = 1.70158;
  279. }
  280. if ((t /= d / 2) < 1) {
  281. return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
  282. }
  283. return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
  284. },
  285. bounceIn: function(t, b, c, d) { //弹球减振(弹球渐出)
  286. return c - Tween['bounceOut'](d - t, 0, c, d) + b;
  287. },
  288. bounceOut: function(t, b, c, d) {
  289. if ((t /= d) < (1 / 2.75)) {
  290. return c * (7.5625 * t * t) + b;
  291. } else if (t < (2 / 2.75)) {
  292. return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
  293. } else if (t < (2.5 / 2.75)) {
  294. return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
  295. }
  296. return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
  297. },
  298. bounceBoth: function(t, b, c, d) {
  299. if (t < d / 2) {
  300. return Tween['bounceIn'](t * 2, 0, c, d) * 0.5 + b;
  301. }
  302. return Tween['bounceOut'](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
  303. }
  304. }
  305. //———————————————————————————————————————————DOM操作———————————————————————————————————————————————————
  306. // 添加节点
  307. // ele: 父节点,支持输入变量,id值,class值,标签值。除变量外,其余值必须为字符串
  308. // node: 添加的标签名,值为字符串
  309. // n: 节点添加个数
  310. // className: 节点绑定的class名,,值为字符串,多个class名用空格隔开
  311. // boolean: 是否选中所有目标父节点。可选参数,不输入则判定为false,则只匹配选中的第一个节点
  312. function addChild(ele, node, n, className, boolean) {
  313. //获取节点
  314. let parent = null;
  315. if (typeof ele !== "string") parent = ele;
  316. else if (ele[0] === "#") parent = document.getElementById(ele.slice(1));
  317. else if (ele[0] === ".") {
  318. if (boolean === false) parent = document.getElementsByClassName(ele.slice(1))[0];
  319. else parent = document.getElementsByClassName(ele.slice(1));
  320. } else {
  321. if (boolean === false) parent = docuemnt.getElementsByTagName(ele)[0];
  322. else parent = document.getElementsByTagNameNS(ele);
  323. }
  324. //声明用于存储父节点及子节点的对象
  325. const obj = {
  326. "parent": parent,
  327. "children": []
  328. };
  329. //添加节点
  330. if (boolean) {
  331. for (let i = 0; i < parent.length; i++) {
  332. //创建容器碎片
  333. const fragment = document.createDocumentFragment();
  334. //保存子节点,用于返回值
  335. obj.children[i] = [];
  336. for (let j = 0; j < n; j++) {
  337. const target = document.createElement(node);
  338. target.className = className;
  339. fragment.appendChild(target);
  340. //添加子节点到数组,用于返回值
  341. obj.children[i][j] = target;
  342. }
  343. parent[i].appendChild(fragment)
  344. }
  345. } else {
  346. //创建碎片容器
  347. const fragment = document.createDocumentFragment();
  348. for (let i = 0; i < n; i++) {
  349. const target = document.createElement(node);
  350. target.className = className;
  351. fragment.appendChild(target);
  352. //添加子节点,用于返回值
  353. obj.children[i] = target;
  354. }
  355. //将碎片容器一次性添加到父节点
  356. parent.appendChild(fragment);
  357. }
  358. //返回参数,供动画函数调用
  359. return obj;
  360. }

3、案例解析

  • HTML

       由于节点很多,并且我想尽量做得逼真有趣一点,就给节点加了随机位置。所以节点的输出都是用JS控制的,HTML这边只写了几个父元素盒子,加上相应的id名和class类名,结构相对简单。

  • CSS

       CSS部分的难点就是流星的样式和用圈圈画云层,然后将云层堆叠出立体效果。

首先说一下流星的样式:

  1. #sky .star {
  2. position: absolute;
  3. opacity: 0;
  4. z-index: 10000;
  5. }
  6. .star::after {
  7. content: "";
  8. display: block;
  9. border: solid;
  10. border-width: 2px 0 2px 80px;
  11. /*流星随长度逐渐缩小*/
  12. border-color: transparent transparent transparent rgba(255, 255, 255, 1);
  13. border-radius: 2px 0 0 2px;
  14. transform: rotate(-45deg);
  15. transform-origin: 0 0 0;
  16. box-shadow: 0 0 20px rgba(255, 255, 255, .3);
  17. }

       先提取了公共样式,添加定位属性;

       然后在star后通过after伪类添加流星,用border特性画:

       1)模型绘制: border-width的顺序为四边top、right、bottom、left,同理border-color的顺序也为四边top、right、bottom、left。这样将border-width与border-color一一对应后,就能看出2px是流星的宽度,80px是流星的长度,而0px就是流星的尾巴。这样就形成了一个头部2px宽,尾部0px,长度80px的流星模型;

       2)稍微逼真: 通过border-radius给流星的头部增加个圆角,让它看起来emmmmmmm更逼真?最后通过roteta旋转一个角度,让它看起来像是往下掉;

       3)增加闪光: 通过box-shadow 给流星增加一点光晕,让它看起来有闪光的效果;



       通过以上3步,一个流星就画好了。

然后是画云:

  1. 因为云的代码比较长,这里就不贴出来了。方法无非是通过一个一个的圆,相互叠加覆盖,完成一个云朵的形状;
  2. 完成一个云层之后,copy一个,然后多个云层通过rotateopacityleft定位等,做出一个渐隐叠加的立体效果;
  • JS

JS部分以流星举例说明

  1. setInterval(function() {
  2. const obj = addChild("#sky", "div", 2, "star"); //插入流星
  3. for (let i = 0; i < obj.children.length; i++) {
  4. //随机位置
  5. const top = -50 + Math.random() * 200 + "px",
  6. left = 200 + Math.random() * 1200 + "px",
  7. scale = 0.3 + Math.random() * 0.5;
  8. const timer = 1000 + Math.random() * 1000;
  9. obj.children[i].style.top = top;
  10. obj.children[i].style.left = left;
  11. obj.children[i].style.transform = `scale(${scale})`;
  12. //添加动画
  13. requestAnimation({
  14. ele: obj.children[i],
  15. attr: ["top", "left", "opacity"],
  16. value: [150, -150, .8],
  17. time: timer,
  18. flag: false,
  19. fn: function() {
  20. requestAnimation({
  21. ele: obj.children[i],
  22. attr: ["top", "left", "opacity"],
  23. value: [150, -150, 0],
  24. time: timer,
  25. flag: false,
  26. fn: () => {
  27. obj.parent.removeChild(obj.children[i]); //动画结束删除节点
  28. }
  29. })
  30. }
  31. });
  32. }
  33. }, 1000);

       这里边用到了我自己封装的两个方法,一个是基于requestAnimationFrame的requestAnimation,以及基于appendChild的addChild

       为了达成星星位置随机的效果,通过定时器setInterval不停的插入删除流星:

       首先,每次添加2个流星到页面,但是定时器的间隔时间小于流星的动画时间,这样就能保证页面中的流星的数量不是一个固定值,但肯定是大于2的。不然一次2个流星略显冷清;

       然后,通过for循环(也可以用for-in,for-of,都行。for-of最简单)给每个新添加到页面中的流星一个随机的位置(top、left)、随机的大小(scale)、随机的动画执行时间(timer);

       最后,在for循环中,给每个新添加到页面中的流星加上动画,并通过回调函数在执行完动画后删除节点。这里要注意的是,动画要分成两个阶段出现消失,主要是opacity控制)。另外我这里的处理,每个流星都移动相同的距离300px,这个距离我觉得也可以通过随机数控制,但我犯了个懒,就没有做。

4、小问题

       目前我发现的问题有2个:

       一是DOM操作本身的问题。页面不停的添加与删除节点,造成不停地回流与重绘,很耗性能;

       二是requestAnimationFrame本身的问题。因为定时器不断在添加节点,而requestAnimationFrame的特性——当离开当前页面去浏览其他页面时,动画会暂停。这就造成了一个问题,节点一直在加,但动画全停在那没有执行。那么下次再回到这个页面的时候,就boom!!!动画就炸了,你会看到画面一卡,很多小蝌蚪集体出动去找妈妈;

5、结语

       这个小案例虽然从难度上来看很简单,但是它可拓展性很高——比如表个白啊、寄个相思、耍个浪漫啊等等(手动狗头doge),而且用纯CSS也可以实现(我也写了一版纯CSS的,因为不能随机位置随机大小,所以看起来比较静态一点,就不贴了)。所以对于了解CSS动画与JS动画,是个很不错的练手小案例。

       感谢看完这篇文章的可爱的人儿,希望你能从中获得灵感如果能给你带来帮助,那我是极高兴地如果你把灵感告诉我,那我就can't happniess anymore了~

       最后,再次感谢,如果有优化写法,欢迎指导~

前端案例分享(一):CSS+JS实现流星雨动画的更多相关文章

  1. CSS+JS实现流星雨动画

    引言 平常会做一些有意思的小案例练手,通常都会发到codepen上,但是codepen不能写分析.        所以就在博客上开个案例分享系列,对demo做个剖析.目的以分享为主,然后也希望各路大神 ...

  2. WEB前端性能优化:HTML,CSS,JS和服务器端优化

    对前端开发工程师来说,前端性能优化的重要性是不言而喻的,最为大家所知的是YSLOW的23条优化规则,在我的理解中,性能优化不纯粹是指用户访问网站的速度,也包括开发的效率,这里我总结下我理解中的WEB前 ...

  3. Mikit前端框架,轻量级CSS&JS前端框架

    Mikit CSS Framework Mikit介绍 Mikit是前端开发人员和前端设计师所喜爱的Web框架.Mikit的创建和设计旨在为前端社区提供最灵活而强大的CSS框架. 与许多其他网络框架不 ...

  4. 前端常用场景总结CSS/JS/插件(实用篇更新中...)

    <div class="box box1"> <span>垂直居中</span> </div> .box1{ display: ta ...

  5. H5案例分享:使用JS判断客户端、浏览器、操作系统类型

    使用JS判断客户端.浏览器.操作系统类型 一.JS判断客户端类型 JS判断客户端是否是iOS或者Android手机移动端 通过判断浏览器的userAgent,用正则来判断手机是否是ios和Androi ...

  6. 【HTML响应式项目】成人教育官网前端页面(HTML+CSS+JS实现三端适应)

    这个页面是在校参赛的小组项目,除首页和所有课程页面以外由组内成员编写,发博客纯属记录. 项目源码已上传至码云仓库:https://gitee.com/ynavc/sss 项目演示地址:http://y ...

  7. 2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...

  8. 1. web前端开发分享-css,js入门篇

    关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...

  9. web前端开发分享-css,js入门篇(转)

    转自:http://www.cnblogs.com/jikey/p/3600308.html 关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人 ...

随机推荐

  1. In-band Network Function Telemetry

    文章名称:In-band Network Function Telemetry 发表时间:2018 期刊来源:SIGCOMM I Introduction (介绍) NFV运行在商品服务器上,在网络功 ...

  2. beat冲刺(4/7)

    目录 摘要 团队部分 个人部分 摘要 队名:小白吃 组长博客:hjj 作业博客:beta冲刺(4/7) 团队部分 后敬甲(组长) 过去两天完成了哪些任务 整理博客 ppt模板 接下来的计划 做好机动. ...

  3. java对文件的操作

    1.按字节读取文件内容2.按字符读取文件内容3.按行读取文件内容 4.随机读取文件内容 public class ReadFromFile {     /**      * 以字节为单位读取文件,常用 ...

  4. Maya学习笔记

    软件: Maya 2016 : 参考教材: Maya 2016 中文版标准教程 ; 改变视图颜色 [窗口]|[设置/首项选择]|[颜色设置]|[3D视图]: 观察视图 旋转视图 Alt + 鼠标左键 ...

  5. Excel中用REPT函数制作图表

    本文从以下七个方面,阐述在Excel中用REPT函数制作图表: 一. 图形效果展示 二. REPT语法解释 三. REPT制作条形图 四. REPT制作漏斗图 五. REPT制作蝴蝶图 六. REPT ...

  6. [转帖].net 4.8 将不再支持win7 win8 版本

    ZT:https://blogs.msdn.microsoft.com/dotnet/2018/07/18/announcing-net-framework-4-8-early-access-buil ...

  7. RHEL/Centos下Sendmail服务器搭建

    目的 Linux下配置Sendmail服务器,并通过客户端验证. 环境 Cento6 局域网(可访问互联网) 内容 配置Sendmail服务器,使得客户端能够通过foxmail或者outlook ex ...

  8. hdu6438 Buy and Resell

    多少年不写题了... (我把每一天看作是一个商品,第i天是第i个商品) 一开始看了半天看出来一个性质:买的所有商品中最贵的不会比卖的所有商品中最便宜的贵,然后似乎没有什么用处.... 所以最后还是看题 ...

  9. DAY6-Flask项目

    1.ViewModel:处理原始数据:裁剪修饰合并 2.访问静态资源 默认情况下,访问的路径为app根目录的下的static文件,为什么说app是根目录而不是fisher.py下,因为在实例化对象的时 ...

  10. Web前端开发神器--WebStorm(JavaScript 开发工具) 8.0.3 中文汉化破解版

    WebStorm(JavaScript 开发工具) 8.0.3 中文汉化破解版 http://www.jb51.net/softs/171905.html WebStorm 是jetbrains公司旗 ...