You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将奇异博士传送门动画定位到翻面卡牌的外圈?

问题

我拥有6张可翻转的卡牌,希望在任意一张卡牌翻面时,将使用p5.js实现的Dr. Strange传送门动画精准显示在卡牌背面彩色圆圈的最外层边框处。当前动画无法正确定位到目标位置,请教如何修改代码实现该效果?

相关代码片段(卡牌旋转与传送门逻辑)

</script>

<script>
//sound effect
$(document).ready(function() {
  var flipSound = document.getElementById("flipSound");

  $(".card").click(function() {
    var card = $(this);

    // 防止重复翻转
    if (!card.hasClass('flipping')) {
      card.addClass('flipping');

      // 判断卡牌当前状态
      if (!card.hasClass("flipped")) {
        flipSound.currentTime = 0;
        flipSound.play();
        startAnimation(); 
        console.log("started")
        card.addClass("flipped");
        } else {
            stopAnimation(); 
            console.log("stopped")
            card.removeClass("flipped");
        }
        
      setTimeout(function() {
        card.removeClass('flipping');
      }, 1000);
    }
  });

  // 点击卡牌背面不触发翻转
  $(".card__back").click(function(e) {
    e.stopPropagation();
  });
});
</script>

<script>
    // 页面hover效果
    document.getElementById("gameCardLink").addEventListener("mouseover", function() {
        document.body.classList.add("hoverEffect");
    });

    document.getElementById("gameCardLink").addEventListener("mouseout", function() {
        document.body.classList.remove("hoverEffect");
    });
</script>

<script>
// 传送门动画
var p5Instance;
function startAnimation() {
    const sketch = (p) => {
        const createParticleSystem = (location) => {
            const origin = location.copy();
            const particles = [];
            const addParticle = velocity => {
                const rand = p.random(0, 1);
                if (rand <= .3) {
                    particles.push(createSparkParticle(origin, velocity.copy()));
                } else {
                    particles.push(createParticle(origin, velocity.copy()));
                }
            };
            const applyForce = force => {
                particles.forEach(particle => {
                    particle.applyForce(force);
                });
            };
            const run = () => {
                particles.forEach((particle, index) => {
                    particle.move();
                    particle.draw();
                    if (particle.isDead()) {
                        particles.splice(index, 1);
                    }
                });
            };
            return { origin, addParticle, run, applyForce };
        };

        const createSparkParticle = (locationP, velocity) => {
            const particle = createParticle(locationP, velocity);
            let fade = 255;
            const draw = () => {
                p.colorMode(p.HSB);
                p.stroke(16, 62, 100, fade);
                const arrow = velocity.copy().normalize().mult(p.random(2, 4));
                const direction = p5.Vector.add(particle.location, arrow);
                p.line(particle.location.x, particle.location.y, direction.x, direction.y);
            };
            const move = () => {
                particle.applyForce(p.createVector(p.random(-.2, .2), p.random(-0.1, -0.4)));
                particle.velocity.add(particle.acc);
                particle.location.add(particle.velocity.copy().normalize().mult(p.random(2, 4)));
                particle.acc.mult(0);
                fade -= 5;
            };
            return { ...particle, draw, move };
        }

        const createParticle = (locationP, velocity) => {
            const acc = p.createVector(0, 0);
            const location = locationP.copy();
            let fade = 255;
            const fadeMinus = p.randomGaussian(15, 2);
            let ligther = 100;
            let situate = 62;
            const draw = () => {
                p.colorMode(p.HSB)
                p.stroke(16, p.constrain(situate, 62, 92), p.constrain(ligther, 60, 100), fade);
                const arrow = velocity.copy().mult(2);
                const direction = p5.Vector.add(location, arrow);
                p.line(location.x, location.y, direction.x, direction.y);
            };
            const move = () => {
                velocity.add(acc);
                location.add(velocity.copy().div(p.map(velocity.mag(), 18, 0, 5, 1)));
                acc.mult(0);
                fade -= fadeMinus;
                ligther -= 8;
                situate += 8;
            };
            const applyForce = force => { acc.add(force); };
            const isDead = () => {
                return fade < 0 || location.x < 0 || location.x > p.width || location.y > p.height;
            };
            return { draw, move, applyForce, isDead, velocity, location, acc };
        };

        const createMover = () => {
            const location = p.createVector(p.width / 2, p.height / 2);
            const velocity = p.createVector(0, 0);
            const acc = p.createVector(0, 0);
            let angle = 0;
            let angleVelocity = 0;
            let angleAcc = 0;
            const particleSystems = Array(9).fill().map(() => createParticleSystem(location));
            
            const getGotoVector = angle => {
                const radius = p.map(angleVelocity, 0, 0.3, 0, 55);
                return p.createVector(
                    location.x + radius * p.cos(angle),
                    location.y + radius * p.sin(angle)
                );
            };
            const draw = () => { particleSystems.forEach(ps => ps.run()); };
            const renderParticleSystem = () => {
                particleSystems.forEach(ps => {
                    const goToVector = getGotoVector(angle - p.random(0, p.TWO_PI));
                    const prepencular = p.createVector(
                        (goToVector.y - location.y)*-1,
                        (goToVector.x - location.x)
                    );
                    prepencular.normalize().mult(angleVelocity * 70);
                    ps.origin.set(goToVector);
                    ps.addParticle(prepencular);
                    ps.applyForce(p.createVector(0, 0.3));
                });
            };
            const move = () => {
                angleAcc = 0.005;
                angleVelocity = p.constrain(angleVelocity + angleAcc, 0, 0.32);
                angle += angleVelocity;
                angleAcc = 0;
                renderParticleSystem();
            };
            return { draw, move };
        };

        let mover;
        p.setup = function() {
            p.createCanvas(240, 320);
            p.clear(); 
            mover = createMover();
        }
        p.draw = function() {
            p.clear();
            mover.move();
            mover.draw();
        }
    };
    p5Instance = new p5(sketch);
}

// 停止动画
function stopAnimation() {
    if (p5Instance) {
        p5Instance.remove();
        p5Instance = null;
    }
}
</script>

修改方案

1. 传递卡牌圆圈的位置与尺寸信息

在卡牌点击事件中,获取背面彩色圆圈的中心坐标和半径,传入动画启动函数:

// 修改卡牌点击逻辑中的startAnimation调用
if (!card.hasClass("flipped")) {
  flipSound.currentTime = 0;
  flipSound.play();
  // 获取圆圈元素(需确保卡牌背面圆圈有对应类名,比如card__back-circle)
  const circle = card.find('.card__back-circle')[0];
  const rect = circle.getBoundingClientRect();
  const centerX = rect.left + rect.width / 2;
  const centerY = rect.top + rect.height / 2;
  const radius = rect.width / 2;
  // 传入参数启动动画
  startAnimation(centerX, centerY, radius);
  console.log("started")
  card.addClass("flipped");
}

2. 调整p5动画的定位与尺寸

修改startAnimation函数,接收位置参数并调整画布定位和粒子生成逻辑:

function startAnimation(centerX, centerY, radius) {
    const sketch = (p) => {
        // 保留原有粒子系统、粒子创建等代码不变

        // 修改createMover,接收中心坐标和半径
        const createMover = (moverCenterX, moverCenterY, circleRadius) => {
            const location = p.createVector(moverCenterX, moverCenterY);
            // ... 保留其余逻辑 ...
            const getGotoVector = angle => {
                // 使用传入的圆圈半径替代固定值
                return p.createVector(
                    location.x + circleRadius * p.cos(angle),
                    location.y + circleRadius * p.sin(angle)
                );
            };
            // ... 保留其余逻辑 ...
        };

        let mover;
        p.setup = function() {
            // 创建与圆圈等大的画布
            p.createCanvas(radius * 2, radius * 2);
            // 设置画布绝对定位到圆圈位置
            p.canvas.style.position = 'absolute';
            p.canvas.style.left = `${centerX - radius}px`;
            p.canvas.style.top = `${centerY - radius}px`;
            p.canvas.style.pointerEvents = 'none'; // 不干扰卡牌点击
            p.clear(); 
            // 以画布中心作为粒子生成中心
            mover = createMover(p.width/2, p.height/2, radius);
        }

        // 保留原有draw函数不变
        p.draw = function() {
            p.clear();
            mover.move();
            mover.draw();
        }
    };
    p5Instance = new p5(sketch);
}

3. 适配背景图式圆圈

如果卡牌背面的圆圈是背景图片的一部分而非独立DOM元素,需手动计算其相对卡牌的位置:

// 假设圆圈在卡牌内的相对位置是居中,直径为卡牌宽度的80%
const cardRect = card[0].getBoundingClientRect();
const circleDiameter = cardRect.width * 0.8;
const radius = circleDiameter / 2;
const centerX = cardRect.left + cardRect.width / 2;
const centerY = cardRect.top + cardRect.height / 2;

内容的提问来源于stack exchange,提问作者user14178341

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 21:14:51