这是效果图
思路分析:
一、烟花,可能会存在多个,每个烟花都是独立的对象,需要在点击的一瞬间被创建。因为点击时才创建对象,所以点击事件之前的过程不属于面向对象的过程
二、提前处理好:选择元素,绑定事件,触发事件时,执行面向对象的启动(new)。
OOA:烟花:点击页面,出现运动的元素到达目的之后,炸开到随机的位置
1.创建主题烟花的元素,设置初始位置,颜色,等信息,插入页面。
2.开始运动到鼠标点击的位置。
3.到目标之后,删除,然后,创建一堆小烟花,设置小烟花的位置(主题烟花消失的位置),颜色,个数和半径,插入页面。
4.小烟花开始运动,运动到随机位置。
5.结束之后删除。
这里我们开始之前先封装一个多属性缓冲运动的函数,方便后面的运动。
//这里ele是元素,data是对象,用来设置属性,end是回调函数,用来进行延迟运动
function move(ele,data,end){
clearInterval(ele.t);
ele.t = setInterval(() => {
// 1.计时器开启之后,设定状态为关闭计时器
var onoff = true;
for(var i in data){
var iNow = parseInt(getStyle(ele,i));
var speed = (data[i] - iNow)/7;
speed = speed<0 ? Math.floor(speed) : Math.ceil(speed);
// 必须所有属性都到目标才能清计时器
// 每次只能拿到一个属性
// 只能判断一个属性是否到目标
// 如果有一个属性到目标了,一定会清除计时器么?不一定
// 如果有一个属性没到目标,一定不清除计时器!!!
if(data[i] != iNow){
// 3.但是在设定状态之后,关闭计时器之前,判断是否有属性没到目标,只要有一个属性没到目标,就把状态改成不关闭计时器
onoff = false;
}
ele.style[i] = iNow + speed + "px";
}
// 2.根据状态决定关闭计时器
if(onoff){
clearInterval(ele.t);
// if(end){
// end();
// }
end && end(); //有一个为false就没有执行的必要。没有end这个回调函数,就不执行;有就执行。
}
}, 30);
}
//获取非行内样式元素属性兼容
function getStyle(ele,attr){
if(getComputedStyle){
return getComputedStyle(ele,false)[attr]; //其他浏览器,只能获取不能设置
}else{
return ele.currentStyle[attr]; //IE,只能获取不能设置
}
}
这是css样式代码:
#container{
width: 80%;
height: 600px;
border: 2px solid red;
background: #000;
margin:20px auto;
cursor: pointer;
position: relative;
left: 0;
top: 0;
overflow: hidden;
}
.fire{
width: 10px;
height:10px;
position: absolute;
bottom: 0;
}
.small-fire{
width: 10px;
height:10px;
position: absolute;
border-radius: 50%;
}
这是html代码
<body>
<div id="container"></div>
</body>
这是js代码:(请务必加上之前的缓冲运动函数)
<script>
var ocont=document.querySelector("#container");
//点击外框,new个主题烟花
ocont.onclick = function (eve) {
var e=eve||window.event;
new Fire({
cont:ocont,
x:e.clientX-this.offsetLeft-5,
y:e.clientY-this.offsetTop-5
});
}
//主题烟花的构造函数
function Fire(options) {
this.cont=options.cont;
this.x=options.x;
this.y=options.y;
this.init();
}
//用来创建主题烟花,放在外框中,给其设置class和初始left
Fire.prototype.init=function () {
this.f=document.createElement("div");
this.f.className = "fire";
this.cont.appendChild(this.f);
this.f.style.background=randomColor();
this.f.style.left=this.x+"px";
this.move();
}
//让主题烟花从外框底部到鼠标点击的位置
Fire.prototype.move=function () {
//大烟花从先到达鼠标的位置然后,消失,生成无数个各个方向飞的小烟花
move(this.f,{top:this.y},()=>{//这里箭头函数可以代替回调函数,让其this指向他的外部
this.f.remove();
this.smallFire();
})
}
//把s变成变量,因为move里回调函数里面的箭头函数没办法每次拿到s的值,所以把var改成let,让move外面的匿名函数的{}变成块级作用域,方便将来查找s。
//想象把move用作用域包起来,不让箭头函数到最后再执行,可以获取到s每次变化的值。
Fire.prototype.smallFire=function () { //定义创建小烟花的功能
var num=random(10,20); //小烟花的个数
var r=random(100,200)//小烟花的半径
for (var i=0;i<num;i++){
//问题1说明:循环中在创建小烟花,如果小烟花的元素保存到实例身上,那么一个属性只能保存一个元素,等待将来的元素时,只能删除最后一个
//问题1解决办法:不保存在实例身上,单独保存成变量。
//问题2说明保存成变量后,由于move里面有定时器,属于异步范畴,在for循环之后执行,重复覆盖,将来只能拿到最后一个
//问题2解决方式:
//1.利用作用域的嵌套(let块级作用域,匿名函数作用域),保存这个变量,方便将来查找;
//2.利用bind将这个变量强行传参,传给this,通过将来的this找到每个小烟花。
let s=document.createElement("div"); //构造小烟花
s.className = "small-fire";
s.style.left=this.x+"px";//以点击位置为中心
s.style.top=this.y+"px";
s.style.background=randomColor();
this.cont.appendChild(s);
// s.setAttribute("index",i);
var target={
x:parseInt(Math.sin(Math.PI/180*(360/num*i))*r)+this.x,//360/num是每个小烟花之间的角度,在用i把它们遍历成一个圆
y:parseInt(Math.cos(Math.PI/180*(360/num*i))*r)+this.y
}
move(s,{ //move函数里面有计时器,在循环之后执行
left:target.x,
top:target.y
},()=>{
s.remove(); //延迟执行的函数里不能拿到for循环里的计数器也就是i,因为for循环立刻执行完,而move函数里的计时器最后执行,只消失一个小火焰,所以要用let,把for循环里面的花括号作为作用域,
})
}
}
function random(a,b) {
return Math.round(Math.random()*(a-b)+b)
}
function randomColor() {
return `rgb(${random(0,255)},${random(0,255)},${random(0,255)})`;
}
</script>
这里的难点就是move函数里有定时器,如何让remove()方法的对象是每个小烟花。
1.利用作用域的嵌套(let块级作用域,匿名函数作用域),保存这个变量,方便将来查找;
2.利用bind将这个变量强行传参,传给this,通过将来的this找到每个小烟花。
3.可以将move方法外面套一个匿名函数,将s变量作为参数传入,自己构造一个作用域。