Giter VIP home page Giter VIP logo

smallromance's Introduction

SmallRomance

要是觉得这个项目还不错,别忘记star哦

快速预览

[土味情话]](https://newnewking.github.io/SmallRomance/)

[虎年春节]](https://newnewking.github.io/2022chunjie/)

开发者调试

本项目是基于nodejs,所以必须要先安装nodeJs nodeJs官网

然后命令行里进入到项目目录下,先输入:

npm install

安装好之后,调试预览。 输入:

npm run dev

应该会自动弹出网页,如果没有,就手动在浏览器输入localhost:8888

此时可以修改文件并预览效果

打包

npm run build

此时会生成一个dist文件夹,里面则是打包后的文件。

若还有问题的童鞋,可以在issue里面留言。

改动配置

大部分的参数配置都在src/config/global.js里面

只需改动参数就可开箱即用。

参数说明相见global.js注释

效果初级教程

烟花效果;

文字粒子效果;

参考项目

在canvas中使用粒子组成文字

偶然发现的github上的一个项目 --- shape-shifter

https://github.com/kennethcachia/shape-shifter

烟花效果

参考了codepen.io上的很多作品。

主要参考 --- fireworks seen in the countryside

fireworks seen in the countryside


licensed under MIT LISENCE

smallromance's People

Contributors

newnewking avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smallromance's Issues

文字粒子效果教程

预览

完整项目预览----预览地址;

粒子效果原理

在canvas中,可以通过getImageData()方法来获取像素数据。

ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, 1, 1);
const imageData = ctx.getImageData(0, 0, 1, 1);

imageData有三个属性:

  • data:数组,包含了像素信息,每个像素会有四个长度,如[255,0,0,255, ... ,255,127,0,255],分别代表该像素的RGBA值。
  • widthimageData对象的宽。
  • heightimageData对象的高。

首先在canvas写上某种颜色文字,再去分析像素数据(比如改像素是否有透明度等),然后自己记录下该像素点的位置

下例是通过改变像素的数据而重新写出来的文字。

ctx.font = 'bold 40px Arial';
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText('你好啊', 60, 20);

document.querySelector('#button').addEventListener('click', function(){
    const imgData = ctx.getImageData(0, 0, 120, 40);
    for(let i = 0;i < imgData.data.length; i+=4){
    	if(imgData.data[i + 3] == 0) continue;
    	imgData.data[i] = 255;
    	imgData.data[i + 1] = 0;
    	imgData.data[i + 2] = 0;
    	// imgData.data[i + 3] = 255;  这个代表的是透明度 透明度不变 255最高 0最低
    }
    ctx.putImageData(imgData,120,0);
});

这段代码只是示例说明一下,实际上才不会有人这么脑残去换颜色吧。

获取点位置

要获取点的位置,首先要将字写在画布上,但是字又不能让别人看到。所以可以动态创建一个画布,这个画布不会append到任何节点上,只会用于写字。

const cache = document.createElement('canvas');

将宽高等与展示的画布设置成一样的。(不贴这部分的代码了)

创建一个对象,用于获取点的位置

const ShapeBuilder = {
    //初始化字的对齐方式等,我认为middle 与 center比较好计算一点
    init(width, height){
        this.width = width;
        this.height = height;
        this.ctx = cache.getContext('2d');
        this.ctx.textBaseline = 'middle';
        this.ctx.textAlign = 'center';
    },
    //获取位置之前必须先要写入文字。 这里的size=40是默认值
    write(words, x, y, size = 40){
        //清除之前写的字。
        this.ctx.clearRect(0, 0, this.width, this.height);
        this.font = `bold ${size}px Arial`;
        this.ctx.fillText(words, x, y);
        //记录当前文字的位置,方便计算获取像素的区域
        this.x = x;
        this.y = y;
        this.size = size;
        this.length = words.length;
    },
    getPositions(){
        //因为imgData数据非常的大,所以尽可能的缩小获取数据的范围。
        const xStart = this.x - (this.length / 2) * this.size, 
            xEnd = this.x + (this.length / 2) * this.size,
            yStart = this.y - this.size / 2, 
            yEnd = this.y + this.size / 2, 
            
            //getImageData(起点x, 起点y, 宽度, 高度);
            data = this.ctx.getImageData(xStart, yStart, this.size * this.length, this.size).data;
            
        //间隔 (下面有介绍)
        const gap = 4;
        
        let positions = [], x = xStart, y = yStart;
        
        for(var i = 0;i < data.length; i += 4 * gap){
            if(data[i+3] > 0){
                positions.push({x, y});	
            }
            
            x += gap;
            
            if(x >= xEnd){
                x = xStart;
                y += gap;
                i += (gap - 1) * 4 * (xEnd - xStart);
            }
        }
        return positions;
    }
}

ShapeBuilder.init();

关于gap:在循环imgData数组的时候,数据量太大可能会造成卡顿,所以可以使用间隔来获取坐标点的方法。不过可能会造成文字部分地方缺失。就需要个人来权衡利弊,自己来调整了。

gap的值必须能被xEnd-xStart给整除,不然会造成获取坐标点错位的后果。

关于canvasmiddlecenter的规则:

this.ctx.font = 'bold 40px Arial';
this.ctx.fillText('你好',40 ,20);

效果如下图所示

fillText设置的坐标点刚好会是整个字的中点,就是图中middlecenter的交点。其实以其它对齐方式也是可以的,看个人喜好。

更多的对齐规则参考HTML 5 Canvas 参考手册的文本。

创建微粒类

微粒应该随机生成,然后移动到指定的位置去。

微粒类的属性:

自身当前位置(x,y), 目标位置:(xEnd,yEnd),自身大小(size),自身颜色(color),移动快慢(e)

方法:go():每一帧都要移动一段距离,render():渲染出微粒(我用心形的形状)

class Particle {
    constructor({x, y, size = 2, color, xEnd, yEnd, e = 60} = {}){
        this.x = x;
        this.y = y;
        this.size = size;
        this.color = color ||  `hsla(${Math.random() * 360}, 90%, 65%, 1)`;
        this.xEnd = xEnd;
        this.yEnd = yEnd;
        
        //经过e帧之后到达目标地点
        this.e = e;
        //计算每一帧走过的距离
        this.dx = (xEnd - x) / e;
		this.dy = (yEnd - y) / e;
    }
    go(){
        //到目的后保持不动 (其实这里也可以搞点事情的)
        if(--this.e <= 0) {
            this.x = this.xEnd;
            this.y = this.yEnd;
            return ;
        }
        this.x += this.dx;
        this.y += this.dy;
    }
    render(ctx){
        this.go();
        //下面是画出心型的贝塞尔曲线
        ctx.beginPath();
        ctx.fillStyle = this.color;
        ctx.moveTo(this.x + 0.5 * this.size, this.y + 0.3 * this.size);
        ctx.bezierCurveTo(this.x + 0.1 * this.size, this.y, this.x, 
                        this.y + 0.6 * this.size, this.x + 0.5 * 
                        this.size, this.y + 0.9 * this.size);
        ctx.bezierCurveTo(this.x + 1 * this.size, this.y + 0.6 * 
                        this.size, this.x + 0.9 * this.size, this.y, 
                        this.x + 0.5 * this.size,
                        this.y + 0.3 * this.size);
        ctx.closePath();
        ctx.fill();
        return true;
    }
}

微粒类最基本的属性与方法就是这些,如果要让粒子更好看一点,或者更生动一点,可以自己添加一些属性与方法。

具体流程

const canvas = {
    init(){
        //设置一些属性
        this.setProperty();
        //创建微粒
        this.createParticles();
        //canvas的循环
        this.loop();
    },
    setProperty(){
        this.ctx = studio.getContext('2d');
        this.width = document.body.clientWidth;
        this.height = document.body.clientHeight;
        this.particles = [];
    },
    createParticles(){
        let dots;
        //ShapeBuilder.write(words, x, y, size)
        ShapeBuilder.write('每个字都是',this.width / 2, this.height / 3, 120);
        dots = ShapeBuilder.getPositions(6);
        ShapeBuilder.write('爱你的模样', this.width / 2, this.height * 2 / 3, 120);
        dots = dots.concat(ShapeBuilder.getPositions(6));
        //dots已经获取到了字的坐标点 
        //每一个微粒的目标地点都是dots的坐标
        //每一个微粒都随机出生在画布的某个位置
        for(let i = 0; i < dots.length; i++){
            this.particles.push(new Particle({
                xEnd:dots[i].x, 
                yEnd:dots[i].y , 
                x: Math.random() * this.width, 
                y: Math.random() * this.height, 
                size:6, 
                color:'hsla(360, 90%, 65%, 1)'
            }));
        }
    },
    loop(){
        //每一帧清除画布,然后再渲染微粒就可以了
        requestAnimationFrame(this.loop.bind(this));
        this.ctx.clearRect(0, 0, this.width, this.height);
        for(var i = 0; i < this.particles.length; i++){
            this.particles[i].render(this.ctx);
        }
    }
}

canvas.init();

如果想要给每个粒子加上小尾巴的话,那么在每一帧的时候,就不要清除画布,而且覆盖一层有透明度的底色。

//修改loop方法
//this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillStyle = 'rgba(0,0,0,0.2)';
this.ctx.fillRect(0, 0, this.width, this.height);

这样的话会变成如下效果

最后

在这这篇文章的时候,并没有注意太多细节,比如gap应该是可以被设置的,或者是一个被特殊标注的常量,而不应该随便写在方法中。对于本例的代码,切勿生搬硬套,重要的是要理解原理,以及自己亲自动手尝试

我也是在写这篇文章的过程中,才发现了之前获取position一个不精准的地方。

这里只讲了粒子效果最基础的用法,实际上还可以做出很多非常炫酷的效果

比如在粒子到达目的地后还可以抖动什么的

粒子形状、颜色的变化等等。

这个项目还可以搞很多事情的,大家也可以自己多来尝试弄些更加炫酷的效果。

烟花效果可以看一下我的上一篇烟花效果教程

完整项目

github项目地址

如果觉得还不错,请star一个吧。

参考项目

github上的一个项目---- shape-shifter

这个项目我觉得非常不错,可惜作者都消失好多年了。

codepen.io 上的一个作品 ---- Love In Hearts

终端设备自适应

const width = util.isPhone() ? document.body.clientWidth : 360;
const height = util.isPhone() ? document.body.clientHeight : 600;
源码中判断是否为移动端时,条件取值反了,建议修改为:
const width = util.isPhone() ? 360 : document.body.clientWidth;
const height = util.isPhone() ? 600 : document.body.clientHeight;

小白求助啊

一直显示在loading... 请问是什么情况
看了审查元素没有问题啊,也有dist文件

如何放在GitHub上的博客中?

前辈,很开心能看到您的这个开源项目,我想知道这个项目是怎么放在您的博客中的?还希望前辈指点迷津。

烟花制作教程

多代码,慎读!!!

预览

完整项目预览----预览地址;

属性设计

烟花状态:烟花应有三个状态:

  1. 升空
  2. 等待炸裂
  3. 炸裂后

烟花:发射点(x, y),爆炸点(xEnd, yEnd),升空后等待炸裂时间(wait),炸裂后微粒个数(count),烟花半径(radius)

烟花炸裂后微粒:自身位置(x, y),自身大小(size),自身速度(rate),最大烟花半径(radius)。

config:为全局变量,以及控制参数,包括画布宽高,设定烟花属性等。

设定全局变量

const config = {
    width: 360,
    height: 600,
    canvases: ['bg', 'firework'],
    skyColor: '210, 60%, 5%, 0.2)',
    fireworkTime:{min:30,max:60},
    //烟花参数本身有默认值 传入undefined则使用默认参数
    fireworkOpt:{
    	x: undefined,
    	y: undefined,
    	xEnd: undefined,
    	yEnd: undefined,
    	count: 300,   //炸裂后粒子数
    	wait: undefined,  //消失后 => 炸裂  等待时间
    }
}

构建微粒类

class Particle{
    //默认值写法 
    constructor({x, y, size = 1, radius = 1.2} = {}){
    	this.x = x;
    	this.y = y;
    	this.size = size;
        
    	this.rate = Math.random(); //每个微粒移动的速度都是随机不同的
    	this.angle = Math.PI * 2 * Math.random(); //每个微粒的偏移角度
    	
    	//每次微粒移动速度分解为横纵坐标的移动。
    	this.vx = radius * Math.cos(this.angle) * this.rate; 
    	this.vy = radius * Math.sin(this.angle) * this.rate;
    }

    go(){
    	this.x += this.vx;
    	this.y += this.vy; 
    	this.vy += 0.02; //重力影响 y越大实际越偏下
    	
    	//空气阻力
    	this.vx *= 0.98;
    	this.vy *= 0.98;
    }
    
    //画出微粒的位置
    render(ctx){
    	this.go();
    	ctx.beginPath();
    	ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
    	ctx.fill();
    }
}

构建烟花类

class Firework{
    constructor({x, y = config.height, xEnd, yEnd, count = 300, wait} = {}){
        //烟花自身属性
    	this.x = x || config.width / 8 + Math.random() * config.width * 3 / 4; 
    	this.y = y;
    	this.xEnd = xEnd || this.x;
    	this.yEnd = yEnd || config.width / 8 + Math.random() * config.width * 3 / 8;
    	this.size = 2;
    	this.velocity = -3;
    	
    	this.opacity = 0.8;
    	this.color = `hsla(${360 * Math.random() | 0},80%,60%,1)`;
    	this.wait = wait || 30 + Math.random() * 30;
    	//微粒个数等
        this.count = count;
    	this.particles = [];
    	this.createParticles();
    
    	this.status = 1;
    }
    //创建微粒
    createParticles(){
    	for(let i = 0;i < this.count; ++i){
            this.particles.push(new Particle({x:this.xEnd, y:this.yEnd}));
    	}
    }
    //升空
    rise(){
    	this.y += this.velocity * 1;
    	this.velocity += 0.005; //升空时产生的阻力
    	//烟花升空到目标位置开始渐隐
    	if(this.y - this.yEnd <= 50){
    		this.opacity = (this.y - this.yEnd) / 50;
    	}
    	//如果到了目标位置 就开始第二个状态
    	if(this.y <= this.yEnd){
    		this.status = 2;
    	}
    }
    
    //渲染烟花  烟花所有动作完成之后返回false
    render(ctx){
        switch(this.status){
            case 1: //升空
    		ctx.save();
    		ctx.beginPath();
    		ctx.globalCompositeOperation = 'lighter';
    		ctx.globalAlpha = this.opacity;
    		ctx.translate(this.x, this.y);
    		ctx.scale(0.8, 2.3);
    		ctx.translate(-this.x, -this.y);
    		ctx.fillStyle = this.color;
    		ctx.arc(this.x + Math.sin(Math.PI * 2 * Math.random()) / 1.2, this.y, this.size, 0, Math.PI * 2, false);
    		ctx.fill();
    		ctx.restore();
    
    		this.rise();
    		return true;
            break;
    	    case 2: //烟花消失阶段,等待炸裂
    		if(--this.wait <= 0){
    		    this.opacity = 1;
    		    this.status = 3;
    		}
    		return true;
    	    break;
    	    case 3: //炸裂之后 渲染烟花微粒
    		ctx.save();
    		ctx.globalCompositeOperation = 'lighter';
    		ctx.globalAlpha = this.opacity;
    		ctx.fillStyle = this.color;
    		for(let i = 0;i < this.particles.length;++i){
		    this.particles[i].render(ctx);
    		}
    		ctx.restore();
    		this.opacity -= 0.01;
    		return this.opacity > 0;
    	    break;
    	    default: 
    		return false;
        }
    }
}

放烟花

const canvas = {
    init: function(){
        //一些属性的设定 可以不用管
    	this.setProperty();
    	this.renderBg();
    	
    	//循环体 **主要
    	this.loop();
    },
    setProperty: function(){
    	this.fireworks = [];
    	this.width = config.width;
    	this.height = config.height;
    	this.fireworkTime = (config.fireworkTime.min + (config.fireworkTime.max - config.fireworkTime.min) * Math.random()) | 0;
    
    	this.bgCtx = document.querySelector('#bg').getContext('2d');
    	this.fireworkCtx = document.querySelector('#firework').getContext('2d');
    },
    renderBg(){
    	this.bgCtx.fillStyle = 'hsla(210, 60%, 5%, 0.9)'
    	this.bgCtx.fillRect(0, 0, this.width, this.height);
    },
    
    loop(){
    	requestAnimationFrame(this.loop.bind(this));
    	this.fireworkCtx.clearRect(0, 0, this.width, this.height);
        
        //随机创建烟花
    	if(--this.fireworkTime <= 0){
            this.fireworks.push(new Firework(config.fireworkOpt));
            //每次到点之后重新设置烟花产生时间 (|0转化为整数)
            this.fireworkTime = (config.fireworkTime.min + (config.fireworkTime.max - config.fireworkTime.min) * Math.random()) | 0;
    	}
    
    	for(let i = this.fireworks.length - 1; i >= 0; --i){
    	    //渲染烟花 (若返回值为false则移除烟花)
            !this.fireworks[i].render(this.fireworkCtx) && this.fireworks.splice(i,1);	
    	}
    
    }
}
canvas.init();

完善

此时烟花是这样的,感觉少了点小尾巴。

现在我们每一帧都是清除了画布,如果要加上小尾巴其实也很简单,每一帧都不要清除画布,而是覆盖一层新的有透明度的天空上去。

//canvas.loop方法

// this.fireworkCtx.clearRect(0, 0, this.width, this.height);
this.fireworkCtx.fillStyle = config.skyColor;
this.fireworkCtx.fillRect(0,0,this.width,this.height);	

这时就变成这样了。

但是,还是缺少了在爆炸瞬间 天空变亮的场景。

那么在画烟花的时候,先会获取一下烟花的颜色以及透明度。

// *****Firework constructor
// this.color = `hsla(${360 * Math.random() | 0},80%,60%,1)`;
this.hue = 360 * Math.random() | 0;
this.color = `hsla(${this.hue},80%,60%,1)`;
// *****Firework 新增实例方法
getSkyColor(){
    const skyColor = {
        //只有炸裂阶段才返回亮度
    	lightness: this.status == 3 ? this.opacity : 0 ,
    	hue: this.hue
    };
    return skyColor;
}
// *****config 修改config的skyColor
// skyColor: 'hsla(210, 60%, 5%, 0.2)',
skyColor: 'hsla({hue}, 60%, {lightness}%, 0.2)',
// canvas.loop方法
//this.fireworkCtx.fillStyle = config.skyColor;
//每次替换色调与亮度值。
this.fireworkCtx.fillStyle = config.skyColor.replace('{lightness}', 5 + this.skyColor.lightness * 15).replace('{hue}' , this.skyColor.hue);

this.skyColor = { //新增
    lightness: 0,
    hue: 210
};
for(let i = this.fireworks.length - 1; i >= 0; --i){
    //新增 天空颜色为最亮的烟花的颜色
    this.skyColor = this.skyColor.lightness >= this.fireworks[i].getSkyColor().lightness ? this.skyColor : this.fireworks[i].getSkyColor();
    !this.fireworks[i].render(this.fireworkCtx) && this.fireworks.splice(i,1);	
}

到现在就算是大功告成了。

完整项目

github项目地址

如果觉得还不错,请star一个吧。

烟花制作参考链接

参考了codepen.io上的很多作品。

主要参考 --- fireworks seen in the countryside

fireworks seen in the countryside

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.