Javascript - 交互式粒子徽标不起作用

时间:2017-09-19 14:07:57

标签: javascript html css animation html5-canvas

我正在通过指示来构建交互式粒子徽标设计,并且似乎无法获得最终产品。这是徽标图像文件 -

Havoc Creative logo

我使用画布结构/背景。这是代码 -



var canvasInteractive = document.getElementById('canvas-interactive');
var canvasReference = document.getElementById('canvas-reference');

var contextInteractive = canvasInteractive.getContext('2d');
var contextReference = canvasReference.getContext('2d');

var image = document.getElementById('img');

var width = canvasInteractive.width = canvasReference.width = window.innerWidth;
var height = canvasInteractive.height = canvasReference.height = window.innerHeight;

var logoDimensions = {
  x: 500,
  y: 500
};

var center = {
  x: width / 2,
  y: height / 2
};

var logoLocation = {
  x: center.x - logoDimensions.x / 2,
  y: center.y - logoDimensions.y / 2
};

var mouse = {
  radius: Math.pow(100, 2),
  x: 0,
  y: 0
};

var particleArr = [];
var particleAttributes = {
  friction: 0.95,
  ease: 0.19,
  spacing: 6,
  size: 4,
  color: "#ffffff"
};

function Particle(x, y) {
  this.x = this.originX = x;
  this.y = this.originY = y;
  this.rx = 0;
  this.ry = 0;
  this.vx = 0;
  this.vy = 0;
  this.force = 0;
  this.angle = 0;
  this.distance = 0;
}

Particle.prototype.update = function() {
  this.rx = mouse.x - this.x;
  this.ry = mouse.y - this.y;
  this.distance = this.rx * this.rx + this.ry * this.ry;
  this.force = -mouse.radius / this.distance;
  if (this.distance < mouse.radius) {
    this.angle = Math.atan2(this.ry, this.rx);
    this.vx += this.force * Math.cos(this.angle);
    this.vy += this.force * Math.sin(this.angle);
  }
  this.x += (this.vx *= particleAttributes.friction) + (this.originX - this.x) * particleAttributes.ease;
  this.y += (this.vy *= particleAttributes.friction) + (this.originY - this.y) * particleAttributes.ease;
};

function init() {
  contextReference.drawImage(image, logoLocation.x, logoLocation.y);
  var pixels = contextReference.getImageData(0, 0, width, height).data;
  var index;
  for (var y = 0; y < height; y += particleAttributes.spacing) {
    for (var x = 0; x < width; x += particleAttributes.spacing) {
      index = (y * width + x) * 4;
      if (pixels[++index] > 0) {
        particleArr.push(new Particle(x, y));
      }
    }
  }
};
init();

function update() {
  for (var i = 0; i < particleArr.length; i++) {
    var p = particleArr[i];
    p.update();
  }
};

function render() {
  contextInteractive.clearRect(0, 0, width, height);
  for (var i = 0; i < particleArr.length; i++) {
    var p = particleArr[i];
    contextInteractive.fillStyle = particleAttributes.color;
    contextInteractive.fillRect(p.x, p.y, particleAttributes.size, particleAttributes.size);
  }
};

function animate() {
  update();
  render();
  requestAnimationFrame(animate);
}
animate();

document.body.addEventListener("mousemove", function(event) {
  mouse.x = event.clientX;
  mouse.y = event.clientY;
});

document.body.addEventListener("touchstart", function(event) {
  mouse.x = event.changedTouches[0].clientX;
  mouse.y = event.changedTouches[0].clientY;
}, false);

document.body.addEventListener("touchmove", function(event) {
  event.preventDefault();
  mouse.x = event.targetTouches[0].clientX;
  mouse.y = event.targetTouches[0].clientY;
}, false);

document.body.addEventListener("touchend", function(event) {
  event.preventDefault();
  mouse.x = 0;
  mouse.y = 0;
}, false);
&#13;
html,
body {
  margin: 0px;
  position: relative;
  background-color: #000;
}

canvas {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
}

img {
  display: none;
  width: 70%;
  height: 400px;
  position: absolute;
  left: 50%;
  transform: translate(-50%, 30%);
}
&#13;
<html>

<body>
  <canvas id="canvas-interactive"></canvas>
  <canvas id="canvas-reference"></canvas>

  <img src="https://i.stack.imgur.com/duv9h.png" alt="..." id="img">

</body>

</html>
&#13;
&#13;
&#13;

我的理解是图像文件必须设置为display: none;,然后需要使用javascript命令重新绘制图像,但我不确定此图像是否兼容。完成后我想要在白色背景上的图像。 举一个例子,最终设计需要与此类似 - Logo particle design

2 个答案:

答案 0 :(得分:1)

来自位图的粒子位置。

要获得您想要的FX,您需要创建一个粒子系统。这只是一个对象数组,每个对象都有一个位置,它们想要的位置(Home),一个定义当前运动的矢量和颜色。

通过读取图像中的像素,可以获得每个粒子的原始位置和颜色。您可以通过在画布上渲染图像来访问像素数据,并使用ctx.getImageData来获取像素数据(注意图像必须位于同一个域或具有CORS标头才能访问像素数据)。当您依次读取每个像素时,如果不是透明的,则为该像素创建一个粒子,并从像素颜色和位置设置颜色和原始位置。

使用requestAnimationFrame调用一个渲染函数,每个帧都会迭代所有粒子,通过一些规则来移动它们,这些规则可以为您提供所需的运动。移动每个粒子后,使用简单的形状(例如fillRect

)将它们渲染到画布上

鼠标互动

要与鼠标进行交互,您需要使用鼠标移动事件来跟踪相对于要渲染的画布的鼠标位置。在更新每个粒子时,还要检查它与鼠标的距离。然后,您可以从或向鼠标推动或拉动粒子(取决于您想要的效果。

渲染速度将限制粒子数。

这些类型的FX的唯一问题是,随着粒子数量的增加,你将推动渲染速度限制。什么可以在一台机器上运行良好,在另一台机器上运行得很慢。

为了避免太慢,并且在某些机器上看起来不太好,你应该考虑一下帧速率并减少粒子数量,如果它运行缓慢的话。为了补偿,您可以增加粒径甚至降低画布分辨率。

瓶颈是每个粒子的实际渲染。当你得到大量的数字时,路径方法真的很糟糕。如果你想要非常高的数字,你必须使用与读取相同的方法直接将像素渲染到位图,当然反过来。

从位图读取的示例简单粒子。

下面的示例使用渲染到画布的文本来创建粒子,使用图像只绘制图像而不是文本。这个例子有点矫枉过正,因为我从我的旧答案中撕掉它。这只是完成各种工作的各种方法的一个例子。

const ctx = canvas.getContext("2d");

const Vec = (x, y) => ({x, y});
const setStyle = (ctx,style) => {    Object.keys(style).forEach(key => ctx[key] = style[key]) }
const createImage = (w,h) => {var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i}
const textList = ["Particles"];
var textPos = 0;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center 
var ch = h / 2;
var globalTime;
var started = false;
requestAnimationFrame(update);

const mouse  = {x : 0, y : 0, button : false}
function mouseEvents(e){
	mouse.x = e.pageX;
	mouse.y = e.pageY;
	mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));

function onResize(){ 
	cw = (w = canvas.width = innerWidth) / 2;
	ch = (h = canvas.height = innerHeight) / 2;
    if (!started) { startIt() }
}

function update(timer){
    globalTime = timer;
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
	if (w !== innerWidth || h !== innerHeight){ onResize() }
	else { ctx.clearRect(0,0,w,h) }
    particles.update();
    particles.draw();	
    requestAnimationFrame(update);
}


function createParticles(text){
    createTextMap(
        text, 60, "Arial", 
        {   fillStyle : "#FF0", strokeStyle : "#F00", lineWidth : 2, lineJoin : "round", },
        { top : 0, left : 0, width : canvas.width, height : canvas.height }
    )
}
// This function starts the animations
function startIt(){
    started = true;
    const next = ()=>{
        var text = textList[(textPos++ ) % textList.length];
        createParticles(text);
        setTimeout(moveOut,text.length * 100 + 12000);
    }
    const moveOut = ()=>{
        particles.moveOut();
        setTimeout(next,2000);
    }
    setTimeout(next,0);
}



// the following function create the particles from text using a canvas
// the canvas used is displayed on the main canvas top left fro reference.
var tCan = createImage(100, 100); // canvas used to draw text
function createTextMap(text,size,font,style,fit){
    const hex = (v)=> (v < 16 ? "0" : "") + v.toString(16);
    tCan.ctx.font = size + "px " + font;
    var width = Math.ceil(tCan.ctx.measureText(text).width + size);
    tCan.width = width;
    tCan.height = Math.ceil(size *1.2);
    var c = tCan.ctx;
    c.font = size + "px " + font;
    c.textAlign = "center";
    c.textBaseline = "middle";
    setStyle(c,style);
    if (style.strokeStyle) { c.strokeText(text, width / 2, tCan.height / 2) }
    if (style.fillStyle) { c.fillText(text, width / 2, tCan.height/ 2) }
    particles.empty();
    var data = c.getImageData(0,0,width,tCan.height).data;
    var x,y,ind,rgb,a;
    for(y = 0; y < tCan.height; y += 1){
        for(x = 0; x < width; x += 1){
            ind = (y * width + x) << 2;  // << 2 is equiv to * 4
            if(data[ind + 3] > 128){  // is alpha above half
                rgb = `#${hex(data[ind ++])}${hex(data[ind ++])}${hex(data[ind ++])}`;
                particles.add(Vec(x, y), Vec(x, y), rgb);
            }
        }
    }
    particles.sortByCol
    var scale = Math.min(fit.width / width, fit.height / tCan.height);
    particles.each(p=>{
        p.home.x = ((fit.left + fit.width) / 2) + (p.home.x - (width / 2)) * scale;
        p.home.y = ((fit.top + fit.height) / 2) + (p.home.y - (tCan.height / 2)) * scale;

    })
        .findCenter() // get center used to move particles on and off of screen
        .moveOffscreen()  // moves particles off the screen
        .moveIn();        // set the particles to move into view.

}

// basic particle
const particle = { pos : null,  delta : null, home : null, col : "black", }
// array of particles
const particles = {
    items : [], // actual array of particles
    mouseFX : {  power : 12,dist :110, curve : 2, on : true },
    fx : { speed : 0.3, drag : 0.6, size : 4, jiggle : 1 },
    // direction 1 move in -1 move out
    direction : 1,
    moveOut () {this.direction = -1; return this},
    moveIn () {this.direction = 1; return this},
    length : 0, 
    each(callback){ // custom iteration 
        for(var i = 0; i < this.length; i++){   callback(this.items[i],i) }
        return this;
    },
    empty() { this.length = 0; return this },
    deRef(){  this.items.length = 0; this.length = 0 },
    sortByCol() {  this.items.sort((a,b) => a.col === b.col ? 0 : a.col < b.col ? 1 : -1 ) },
    add(pos, home, col){  // adds a particle
        var p;
        if(this.length < this.items.length){
            p = this.items[this.length++];
            p.home.x = home.x;
			p.home.y = home.y;
            p.delta.x = 0;
            p.delta.y = 0;
            p.col = col;
        }else{
            this.items.push( Object.assign({}, particle,{ pos, home, col, delta : Vec(0,0) } ) );
            this.length = this.items.length
        }
        return this;
    },
    draw(){ // draws all
        var p, size, sizeh;
        sizeh = (size = this.fx.size) / 2;
        for(var i = 0; i < this.length; i++){
            p = this.items[i];
            ctx.fillStyle = p.col;
            ctx.fillRect(p.pos.x - sizeh, p.pos.y - sizeh, size, size);
        }
    },
    update(){ // update all particles
        var p,x,y,d;
        const mP = this.mouseFX.power;
        const mD = this.mouseFX.dist;
        const mC = this.mouseFX.curve;
        const fxJ = this.fx.jiggle;
        const fxD = this.fx.drag;
        const fxS = this.fx.speed;

        for(var i = 0; i < this.length; i++){
            p = this.items[i];
            p.delta.x += (p.home.x - p.pos.x ) * fxS + (Math.random() - 0.5) * fxJ;
            p.delta.y += (p.home.y - p.pos.y ) * fxS + (Math.random() - 0.5) * fxJ;
            p.delta.x *= fxD;
            p.delta.y *= fxD;
            p.pos.x += p.delta.x * this.direction;
            p.pos.y += p.delta.y * this.direction;
            if(this.mouseFX.on){
                x = p.pos.x - mouse.x;
                y = p.pos.y - mouse.y;
                d = Math.sqrt(x * x + y * y);
                if(d < mD){
                    x /= d;
                    y /= d;
                    d /= mD;
                    d = (1-Math.pow(d, mC)) * mP;
                    p.pos.x += x * d;
                    p.pos.y += y * d;        
                }
            }
        }
        return this;
    },
    findCenter(){  // find the center of particles maybe could do without
        var x,y;
        y = x = 0;
        this.each(p => { x += p.home.x; y += p.home.y });
        this.center = Vec(x / this.length, y / this.length);
        return this;
    },
    moveOffscreen(){  // move start pos offscreen
        var dist,x,y;
        dist = Math.sqrt(this.center.x * this.center.x + this.center.y * this.center.y);
        
        this.each(p => {
            var d;
            x = p.home.x - this.center.x;
            y = p.home.y - this.center.y;
            d =  Math.max(0.0001,Math.sqrt(x * x + y * y)); // max to make sure no zeros
            p.pos.x = p.home.x + (x / d)  * dist;
            p.pos.y = p.home.y + (y / d)  * dist;
        });
        return this;
    },
}
canvas { position : absolute; top : 0px; left : 0px; background : black;}
<canvas id="canvas"></canvas>

答案 1 :(得分:0)

使用另存为PNG-8的png并允许跨域

我看到了Bricks and mortar上的精彩文章,并认为我会尝试一下。

我与之抗争了一个永恒,以为我的js是错误的...原来,该图像必须另存为PNG-8,而不能使用抖动而不是PNG-24。

然后确保将crossOrigin =“ Anonymous”属性添加到图像标签:

<img crossOrigin="Anonymous" id="img" src="[link to wherever you host the image]" alt="logo">

我还通过添加以下样式来隐藏参考画布:

canvas#canvas-reference {
  display: none;
}

Photoshop settings for when you save the png

我还添加了一个反跳和调整大小功能,因此它具有响应能力。

结果:

enter image description here

请参阅带有倒置徽标的Demo