【问题标题】:Javascript - interactive particle logo not workingJavascript - 交互式粒子徽标不起作用
【发布时间】:2018-02-28 08:52:28
【问题描述】:

我正在按照说明构建交互式粒子徽标设计,但似乎无法获得成品。这是徽标图像文件 -

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

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);
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%);
}
<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>

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

【问题讨论】:

  • Uncaught ReferenceError: canvasInteractive is not defined... 您缺少一些代码(可能仅来自 sn-p)。可以更新一下吗?
  • @msanford 抱歉,我已经更新了。但是,它仍然无法正常工作。
  • 您需要在画布中绘制图像,而不是使用img 标签
  • @Finiox 哦。我一直遵循的说明说我必须在画布标签下方包含 img 标签,所以这是不正确的吗?
  • 有一个简单的库,用P5绘制东西,url:p5js.org/reference

标签: javascript html css animation html5-canvas


【解决方案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;}
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

    【解决方案2】:

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

    我看到了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;
    }
    

    我还添加了一个去抖动和调整大小的功能,所以它是响应式的。

    结果:

    查看带有倒置徽标的Demo

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-06
      • 1970-01-01
      • 2012-04-27
      • 1970-01-01
      • 1970-01-01
      • 2018-03-21
      • 2013-09-27
      相关资源
      最近更新 更多