实践pixijs结合GSAP实现灵动前端动画 【资源来自 | 猿创营】

314 阅读3分钟

现在,前端页面只是简单展示静态资源已经不能满足各位高级审美用户的感官,页面交互越来越离不复杂又生动的动画交互。最近也正好在研究前端页面的动画交互,然后大帅又提供了一些学习资源,今天就用它的资源出一篇pixijs和GSAP实现动态交互的案例。

最终效果

Kapture 2022-07-30 at 14.46.21.gif

思路拆解

1、效果实现主要2部分组成:
    - 资源画布
    - 资源动画
2、PIXIjs主要负责画布和资源加载管理
3、GSAP主要负责对资源添加动画

实现最小

作为熟悉这两个库的新手,实现当然是从最小的完整功能开始:绘制一个动态按钮

  1. 创建画布
    //初始化canvas
    this.app = new PIXI.Application({
        width: window.innerWidth, // canves宽
        height: window.innerHeight, // canvas高
        backgroundColor: 0xff000 // canvas背景色
    })
    document.querySelector(selector).appendChild(this.app.view)
  1. 添加资源
    //source loader
    this.loader = new PIXI.Loader() //资源加载器
    this.loader.add("btn.png", "images/btn.png") //添加资源
            .add("brake_bike.png", "images/brake_bike.png")
            .add("brake_handlerbar.png", "images/brake_handlerbar.png")
            .add("brake_lever.png", "images/brake_lever.png")
            .add("btn_circle.png", "images/btn_circle.png")

    this.stage = this.app.stage
    this.loader.load() //加载
    this.loader.onComplete.add(() => {
            this.show()
    })//加载完成的回调方法
  1. 绘制按钮并添加动效
    createActionBtn() {
        //按钮是多个图片组装而成,为了方便管理和操控,给按钮一个放置资源的container
        const actionBtn = new PIXI.Container() 
        this.stage.addChild(actionBtn)//添加container到canvas
        
        //获取按钮图片资源
        const imageBtn = new PIXI.Sprite(this.loader.resources['btn.png'].texture)
        const circleBtn = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture)
        const circleBtn2 = new PIXI.Sprite(this.loader.resources['btn_circle.png'].texture)
        //添加资源到创建的container容器中
        actionBtn.addChild(imageBtn)
        actionBtn.addChild(circleBtn)
        actionBtn.addChild(circleBtn2)
        //调整图片对齐的点为中心点
        imageBtn.pivot.x = imageBtn.pivot.y = imageBtn.width * 0.5
        circleBtn.pivot.x = circleBtn.pivot.y = circleBtn.width * 0.5
        circleBtn2.pivot.x = circleBtn2.pivot.y = circleBtn2.width * 0.5

        //animation by gsap
        circleBtn.scale.x = circleBtn.scale.y = 0.8
        gsap.to(circleBtn.scale, { duration: 1, x: 1.3, y: 1.3, repeat: -1 })
        gsap.to(circleBtn, { duration: 1, alpha: 0, repeat: -1 })

        return actionBtn
    }
  1. 效果
截屏2022-07-30 10.19.47.png

完整功能实现

1.加载车和把手,并固定到屏幕的右下角(避免窗口size改变的时候,canvas素材移位)

    const bikeContainer = new PIXI.Container() // bike的容器
    this.stage.addChild(bikeContainer) // 添加容器到canvas
    bikeContainer.scale.x = bikeContainer.scale.y = 0.4 // 因为图片太大,需要缩小到可视区域
    //加载资源
    const bikeImage = new PIXI.Sprite(this.loader.resources['brake_bike.png'].texture)
    const handlerbar = new PIXI.Sprite(this.loader.resources['brake_handlerbar.png'].texture)
    const bikelever = new PIXI.Sprite(this.loader.resources['brake_lever.png'].texture)
    //添加资源到容器【注意:资源层级关系=》后添加的在最上层】
    bikeContainer.addChild(bikelever)
    bikeContainer.addChild(bikeImage)
    bikeContainer.addChild(handlerbar)
    //调整车把的位置,和车头结合
    bikelever.pivot.x = bikelever.pivot.y = 455
    bikelever.x = 722
    bikelever.y = 900
    //调整车体的位置,始终放置在右下角
    const resize = () => {
            bikeContainer.x = window.innerWidth - bikeContainer.width
            bikeContainer.y = window.innerHeight - bikeContainer.height
            actionBtn.x = window.innerWidth - bikeContainer.width * 0.5
            actionBtn.y = window.innerHeight - bikeContainer.height * 0.5
    }

    window.addEventListener("resize", resize)
    resize()

车把位置调整前

车把位置调整前

车把位置调整后

车把位置调整后

  1. 按钮的点击事件和车把效果实现
    actionBtn.interactive = true //让按钮响应事件
    actionBtn.buttonMode = true // 类似css的 cursor: pointer;
    // 添加鼠标down和up的事件
    actionBtn.on("mousedown", () => {
            // bikelever.rotation = (Math.PI / 180) * -30
            gsap.to(bikelever, { duration: 0.2, rotation: (Math.PI / 180) * -30 }) //实现车把刹车的动画
    })
    actionBtn.on("mouseup", () => {
            // bikelever.rotation = 0
            gsap.to(bikelever, { duration: 0.2, rotation: 0 })
    })

3.加速的粒子动销

粒子动销的逻辑整理:

刹车前

刹车前,快速移动,形状如点如线,方向要和车前进方向一致,思路如下:

  • 随机创建n个粒子,放置在一个统一容器中,然后旋转容器方向和车体平行,
  • 让全部粒子都超一个方向移动【Y轴】
  • 移动中,改变粒子的形状,视觉上呈现点线状

刹车后

  • 恢复全部粒子的初始状态

代码实现:

    //create particles with diffrernt color and move by a direction
    //创建容器
    const particlesContainer = new PIXI.Container()
    this.stage.addChild(particlesContainer)
    //设置容器的中心点
    particlesContainer.pivot.x = window.innerWidth / 2
    particlesContainer.pivot.y = window.innerHeight / 2
    //让容器和canvas中心对齐
    particlesContainer.x = window.innerWidth / 2
    particlesContainer.y = window.innerHeight / 2
    //旋转容器
    particlesContainer.rotation = 35 * (Math.PI / 180)
    // 粒子的数组
    const particles = []
    // 粒子颜色数组
    const colors = [0xf1cf54, 0xb5cea8, 0xf1cf54, 0x818181, 0x000000]
    // 创建每个粒子对象
    for (let i = 0; i < 10; i++) {
            const gr = new PIXI.Graphics()
            gr.beginFill(colors[Math.floor(Math.random() * colors.length)])
            gr.drawCircle(0, 0, 6)
            gr.endFill()

            const pItem = {
                    sx: Math.random() * window.innerWidth,
                    sy: Math.random() * window.innerHeight,
                    gr: gr
            }

            gr.x = pItem.sx
            gr.y = pItem.sy

            particlesContainer.addChild(gr)
            particles.push(pItem)
    }
    // 粒子初速度
    let speed = 0
    // 粒子加速移动的方法
    function loop() {
            speed += 0.5
            speed = Math.min(speed, 30)
            for (let i = 0; i < particles.length; i++) {
                    const item = particles[i];
                    item.gr.y += speed

                    if (speed >= 20) {
                            item.gr.scale.y = 40
                            item.gr.scale.x = 0.03
                    }
                    // 移出屏幕后,重新回到顶部
                    if (item.gr.y > window.innerHeight) {
                            item.gr.y = 0
                    }
            }
    }
    // 开始动画
    function start() {
            speed = 0
            gsap.ticker.add(loop)
    }
    // 停止动画
    function stop() {
            gsap.ticker.remove(loop)
            for (let i = 0; i < particles.length; i++) {
                    const item = particles[i];
                    item.gr.scale.y = 1
                    item.gr.scale.x = 1
                    gsap.to(item.gr, { duration: 0.6, x: item.sx, y: item.sy, ease: 'elastic.out' })// 停止的时候加一个会弹动画
            }

    }

将start和stop方法加入到按钮的方法中:

// 添加鼠标down和up的事件
    actionBtn.on("mousedown", () => {
            // bikelever.rotation = (Math.PI / 180) * -30
            gsap.to(bikelever, { duration: 0.2, rotation: (Math.PI / 180) * -30 }) //实现车把刹车的动画
            stop()
    })
    actionBtn.on("mouseup", () => {
            // bikelever.rotation = 0
            gsap.to(bikelever, { duration: 0.2, rotation: 0 })
            start()
    })

最终效果

Kapture 2022-07-30 at 14.46.21.gif

文章到这里就结束了,这是第一篇掘金文章,一个新开端。 感谢文章资源和技术的提供者大帅。

【 在公众号里搜 大帅老猿,很靠谱的一个技术分享和项目交付大牛 】