通过canvas画出爱心图案,表达你的爱意!

在这里插入图片描述
通过canvas画出爱心图案,浏览器可以使用以下js代码,新建对象时,会自动呈现动画效果,代码文末可下载。

点击免费下载源码

let HeartCanvas = new HeartCanvas()

/**
 * 爱心
 * Heart Canvas
 */

class HeartCanvas {
    /**
     * @param hMin 颜色 h 最小值
     * @param hMax 颜色 h 最大值
     * @param countHeart 心的数量
     * @param sizeMin 心形最小值
     * @param sizeMax 心形最大值
     * @param bgColor 背景颜色
     */
    constructor(hMin, hMax, countHeart = 150, sizeMin = 50, sizeMax = 350, bgColor) {
        this.isPlaying = true // 默认自动播放

        this.mouseX = 0
        this.mouseY = 0

        this.configFrame = {
            width : 1200,
            height: 300,
            bgColor: bgColor
        }
        this.configHeart = {
            timeLine: 0,                           // 时间线

            timeInit: new Date().getTime(),
            movement: 1,                           // 运动速度

            x: 50,                                 // 位置 x
            y: 50,                                 // 位置 y
            width: 200,                            // heart 大小
            height: 200,                           // heart 大小
            countHeart: countHeart || 150,         // heart 数量
                                                   // 大小
            sizeMin: isNaN(sizeMin) ?50: sizeMin,  // 最小值
            sizeMax: isNaN(sizeMax) ?350: sizeMax, // 最大值

            // 颜色
            colorSaturate: 100,                    // 颜色饱和度 0-100
            colorLight: 60,                        // 颜色亮度 0-100
            hMin: isNaN(hMin) ?330: hMin,          // 色值最小
            hMax: isNaN(hMax) ?350: hMax,          // 色值最大
            minOpacity: 20,                        // 透明度最小 %
            maxOpacity: 100,                       // 透明度最大 %
            opacityGrowth: 5,                      // 透明度增长值

            // 出现的位置范围
            heartRangeMin: 0,                      // 心出现的位置,从下面算起,取值 0.0-1.0
            heartRangeMax: 0.3,

            // 加速度
            gravityMin: 1,                         // 加速度 min
            gravityMax: 9.8,                       // 加速度 max

            flowDirection: 1,                      // 运动方向 1 向上 -1 向下

        }

        this.heartBuffer = [] // 心缓存

        this.init()

        window.onresize = () => {
            this.configFrame.height = innerHeight * 2
            this.configFrame.width = innerWidth * 2
            let heartLayer = document.getElementById('heartLayer')
            this.updateFrameAttribute(heartLayer)
        }
    }

    play(){
        if (this.isPlaying){

        } else {
            this.isPlaying = true
            this.draw()
        }
    }
    stop(){
        this.isPlaying = false
    }

    moveDown(){
        this.configHeart.flowDirection = -1
    }
    moveUp(){
        this.configHeart.flowDirection = 1
    }

    speedUp(){}
    speedDown(){}

    destroy(){
        this.isPlaying = false
        let heartLayer = document.getElementById('heartLayer')
        heartLayer.remove()
        console.log('动画已停止')
    }

    updateFrameAttribute(heartLayer){
        heartLayer.setAttribute('id', 'heartLayer')
        heartLayer.setAttribute('width', this.configFrame.width)
        heartLayer.setAttribute('height', this.configFrame.height)
        heartLayer.style.width = `${this.configFrame.width / 2}px`
        heartLayer.style.height = `${this.configFrame.height / 2}px`
        heartLayer.style.zIndex = '-3'
        heartLayer.style.userSelect = 'none'
        heartLayer.style.position = 'fixed'
        heartLayer.style.top = '0'
        heartLayer.style.left = '0'
    }


    init(){
        this.configFrame.height = innerHeight * 2
        this.configFrame.width = innerWidth * 2

        let heartLayer = document.createElement("canvas")
        this.updateFrameAttribute(heartLayer)
        document.documentElement.append(heartLayer)

        this.configHeart.timeLine =  0

        // 填充缓存形状
        for (let i = 0; i < this.configHeart.countHeart; i++) {
            let randomSize = randomInt(this.configHeart.sizeMin, this.configHeart.sizeMax)
            let x = randomInt(0, this.configFrame.width)
            let y = randomInt(this.configFrame.height * (1-this.configHeart.heartRangeMax), this.configFrame.height * (1-this.configHeart.heartRangeMin))
            this.heartBuffer.push({
                id: i,
                gravity: randomFloat(this.configHeart.gravityMin, this.configHeart.gravityMax),
                opacity: 0,
                opacityFinal: randomInt(this.configHeart.minOpacity, this.configHeart.maxOpacity), // 最终透明度
                timeInit: randomInt(1, 500), // 随机排布初始 heart 的位置
                x, // 位置 x
                y, // 位置 y
                originalX: x,
                originalY: y,
                width: randomSize,  // heart 大小
                height: randomSize, // heart 大小
                colorH: randomInt(this.configHeart.hMin, this.configHeart.hMax)
            })
        }

        this.draw()

        document.documentElement.addEventListener('mousemove', event => {
            this.mouseX = event.x
            this.mouseY = event.y
        })
    }

    // 判断鼠标跟 box 的距离
    isMouseIsCloseToBox(box){
        let distance = Math.sqrt(Math.pow(Math.abs(box.position.x / 2 - this.mouseX),2) + Math.pow(Math.abs(box.position.y /2  - this.mouseY), 2))
        return distance < 100
    }


    draw() {
        // 建立自己的时间参考线,消除使用系统时间时导致的切换程序后时间紊乱的情况
        this.configHeart.timeLine = this.configHeart.timeLine + 1

        // create heart
        let canvasHeart = document.getElementById('heartLayer')
        let contextHeart = canvasHeart.getContext('2d')
        contextHeart.clearRect(0, 0, this.configFrame.width, this.configFrame.height)


        // 背景,没有 bgColor 的时候,背景就是透明的
        if (this.configFrame.bgColor){
            contextHeart.fillStyle = this.configFrame.bgColor
            contextHeart.fillRect(0,0,this.configFrame.width, this.configFrame.height)
        }


        this.heartBuffer.forEach(heart => {
            // 当出了画面时
            if (heart.y < -(heart.height)){
                heart.y = heart.originalY
                heart.timeInit = this.configHeart.timeLine // 重新定位时间节点
                heart.opacity = 0
            }

            // 当透明度到达最终透明度时
            let timeGap = this.configHeart.timeLine - heart.timeInit // 时间为正数时才计算透明度
            if (timeGap > 0){
                heart.opacity = heart.opacity * ((this.configHeart.timeLine - heart.timeInit)/100)
            } else { // 没到该 heart 的展示时间时,透明度为 0,不显示
                heart.opacity = 0
            }

            if (heart.opacity >= heart.opacityFinal){
                heart.opacity = heart.opacityFinal // 定位到最终的透明度
            }

            // 1/2 gt㎡  运动轨迹
            // let movement = 1/2 * this.configHeart.gravity * Math.pow((new Date().getTime() - heart.timeInit)/1000,2)

            // speed = 1/2 gt
            let movement = 1/2 * heart.gravity * (this.configHeart.timeLine - heart.timeInit) / 300 * this.configHeart.flowDirection
            heart.y = heart.y - movement

            this.drawHeart(
                heart.x,
                heart.y,
                heart.width / 2,
                heart.height / 2,
                `hsl(${heart.colorH} ${this.configHeart.colorSaturate}% ${this.configHeart.colorLight}% / ${heart.opacity}%)`
            )
            heart.opacity = heart.opacity + this.configHeart.opacityGrowth

        })

        if (this.isPlaying) {
            window.requestAnimationFrame(() => {
                this.draw()
            })
        }
    }

    // 画一个心
    drawHeart(x, y, width, height, colorFill) {

        let canvasHeart = document.getElementById('heartLayer')
        let contextHeart = canvasHeart.getContext('2d')

        contextHeart.save()
        contextHeart.beginPath()
        let topCurveHeight = height * 0.3
        contextHeart.moveTo(x, y + topCurveHeight)
        // top left curve
        contextHeart.bezierCurveTo(
            x, y,
            x - width / 2, y,
            x - width / 2, y + topCurveHeight
        )
        // bottom left curve
        contextHeart.bezierCurveTo(
            x - width / 2, y + (height + topCurveHeight) / 2,
            x, y + (height + topCurveHeight) / 1.4,
            x, y + height
        )
        // bottom right curve
        contextHeart.bezierCurveTo(
            x, y + (height + topCurveHeight) / 1.8,
            x + width / 2, y + (height + topCurveHeight) / 2,
            x + width / 2, y + topCurveHeight
        )
        // top right curve
        contextHeart.bezierCurveTo(
            x + width / 2, y,
            x, y,
            x, y + topCurveHeight
        )
        contextHeart.closePath()
        contextHeart.fillStyle = colorFill
        contextHeart.fill()
        contextHeart.restore()
    }

}





/**
 * 输出随机 1 或 -1
 * @returns {number}
 */
function randomDirection(){
    let random = Math.random()
    if (random > 0.5){
        return 1
    } else {
        return -1
    }
}

/**
 * 生成随机整数
 * @param min
 * @param max
 * @returns {number}
 */
function randomInt(min, max){
    return Number((Math.random() * (max - min) + min).toFixed(0))
}

/**
 * 生成随机整数
 * @param min
 * @param max
 * @returns {number}
 */
function randomFloat(min, max){
    return Number(Math.random() * (max - min) + min)
}

Vue可通过以下方式导入,代码在文末可下载:

import {HeartCanvas} from "animate-heart-canvas"

export default {
    mounted() {
        this.height = innerHeight
        this.animateHeartCanvas = new AnimateHeartCanvas()
    },
    beforeDestroy() {
        this.animateHeartCanvas.destroy()
    }
}

API

  • @param hMin 颜色 h 最小值
  • @param hMax 颜色 h 最大值
  • @param countHeart 心的数量
  • @param sizeMin 心形最小值
  • @param sizeMax 心形最大值
  • @param bgColor 背景颜色

let HeartCanvas = new HeartCanvas(hMin, hMax, countHeart, sizeMin, sizeMax, bgColor)
// hMin hMax 对应 hue 的颜色值
// 什么参数都不写就是红色

let 五颜六色 = new HeartCanvas(0, 360) // 这个就是五颜六色的

// 其它操作
HeartCanvas.play() // 心动起来
HeartCanvas.stop() // 心不动
HeartCanvas.moveUp() // 心向上走
HeartCanvas.moveDown() // 心向下走

点击免费下载源码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联小助手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值