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

news2024/9/20 1:13:09

在这里插入图片描述
通过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() // 心向下走

点击免费下载源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么恢复本地磁盘里的数据?电脑本地磁盘数据恢复7种方案

演示机型&#xff1a;技嘉 H310M HD22.0系统版本&#xff1a;Windows 10 专业版软件版本&#xff1a;云骑士数据恢复软件3.21.0.17本地磁盘是什么意思&#xff1f;所谓的本地磁盘是指安装在电脑主板上&#xff0c;不能随便拔插的硬盘&#xff0c;通俗易懂的讲就是电脑内部安装的…

Spring Cloud融合gateway构建的API网关服务 | Spring Cloud 12

一、Spring Cloud Gateway 1.1 概述 所谓的网关就是指系统的统一入口&#xff0c;它封装了运用程序的内部结构&#xff0c;为客户端提供统一的服务&#xff0c;一些与业务功能无关的公共逻辑可以在这里实现&#xff0c;诸如认证、鉴权、监控、路由转发等。 Spring Cloud Gat…

北斗导航 | PPP-RTK:CLASLIB 0.7.2 版本中文手册(CLASLIB ver. 0.7.2 Manual)

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== CLASLIB ver. 0.7.2 Manual

Hevc变换系数扫描

量化后变换系数的熵编码在整个熵编码中占有举足轻重的地位&#xff0c;由于量化后变换系数大多为零值或者幅度较小的值&#xff0c;如何有效利用这一特性是熵编码的关键环节&#xff0c;H265/HEVC标准中&#xff0c;亮度数据和色度数据均以变换块TB为单位&#xff0c;通过编码非…

Compose 动画 (四) : AnimatedVisibility 各种入场和出场动画效果

AnimatedVisibility中的EnterTransition 和 ExitTransition &#xff0c;用来配置入场/出场时候的动画效果。 默认的入场效果是 fadeIn() expandVertically() 默认的出场效果是 fadeOut() shrinkVertically() 1. EnterTransition和ExitTransition支持的动画 enter的参数类…

【VUE】vue3.0后台常用模板

vue3.0后台常用模板&#xff1a; 1、vue-admin-perfect 在线预览 gitee国内访问地址&#xff1a;https://yuanzbz.gitee.io/vue-admin-perfect/#/home github site : https://zouzhibin.github.io/vue-admin-perfect/ 基础功能版本预览&#xff1a;https://yuanzbz.gitee.io/…

上海亚商投顾:沪指失守3300点 两市上涨股不足500只

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。市场情绪沪指今日冲高回落&#xff0c;午后跌幅扩大至1%&#xff0c;失守3300点关口&#xff0c;深成指、创业板指跌近2%。通…

Springboot 定时任务注入FeignClient

问题引入: 在springboot 项目写了个定时任务,里面有段代码通过Feign 调用远程服务,发现通过接口调用可以程序正常执行, 通过配置定时任务发现定时任务没执行,看日志是报了NP.问题跟踪: 写了个demo 重现以上错误:Api(tags "XXX控制器") RestController RequestMapp…

认识代码之前,请先认识你自己 |《编程人生》

这是我的湛庐课程《给技术人的职场突围课》 &#xff08;链接&#xff09; 的一部分。 这篇文章也是 IT 女神征文活动 的一部分。 《编程人生》是一本优秀程序员的采访集&#xff0c;里面记录了15位世界级编程大师的故事。 我在 发刊词 里面说过&#xff0c;在这个书单课里&am…

如何有效地降低软件开发风险?

1、科学分析风险 高风险自动预警 一般对风险进行科学分析&#xff0c;主要从3个维度进行划分&#xff1a;影响的严重性、发生的可能性、产生的影响性。 根据风险对项目的影响程度&#xff0c;从3个维度将其划分5个等级&#xff1a;很低、比较低、中等、比较高、很高。这样我们能…

react router零基础使用教程

安装既然学习 react router 就免不了运行 react安装 reactnpx create-react-app my-appcd my-appnpm start安装 react routernpm install react-router-dom如果一切正常&#xff0c;就让我们打开 index.js 文件。配置路由引入 react-router-dom 的 RouterProviderimport {Route…

JavaWeb--Filter

Filter1 Filter概述2 Filter快速入门2.1 开发步骤2.2 代码演示3 Filter执行流程4 Filter拦截路径配置5 过滤器链5.1 概述5.2 代码演示5.3 问题6 案例6.1 需求6.2 分析6.3 代码实现6.3.1 创建Filter6.3.2 编写逻辑代码6.3.3 测试并抛出问题6.3.4 问题分析及解决6.3.5 过滤器完整…

智慧供热|供热末端(住户)管网远程监测方案

智慧供热通过对供热相关数据的采集、分析和对热源、热网、末端&#xff08;住户&#xff09;的各个供热环节进行智能调控&#xff0c;从而进一步实现热网资源的配置优化&#xff0c;提高热网输送的能力。供热行业存在问题&#xff1a;供热企业目前面临的主要问题还是资金周转困…

MYSQL1

MySQL基本11、MySQL 中有哪几种锁&#xff1f;2、MySQL 中有哪些不同的表格&#xff1f;2、什么是存储引擎3、MySQL 中 InnoDB 支持的四种事务隔离级别名称&#xff0c;以及逐级之间的区别4、CHAR 和 VARCHAR 的区别1、固定长度 & 可变长度2、存储方式3、存储容量4、CHAR会…

cookie session Token终极理解

左边 浏览器 右边 服务器 浏览器发送请求 服务器接收请求 并生成cookie 浏览器查看保存了哪些cookie 用户名密码放在cookie是很不安全的 因为浏览器一旦被攻击泄露 是很危险的 接着诞生了session 会话 sessionID &#xff08;一段杂乱的字母标识&#xff09; 会话结束时间 …

【云原生】Istio请求路由、流量转发、超时配置等

代码继续接着前面的文章【云原生】整合K8s SpringCloudK8s gRpc RocketMQ Istio Envoy&#xff0c;本篇文章我们测试下请求路由功能。生产中我们上了个新接口或者新功能&#xff0c;一般会经过 内灰 -> 外灰5% -> 外灰10% ...... 外灰100%的过程&#xff0c;这篇文章…

计算机图形学08:中点BH算法绘制抛物线(100x = y^2)

作者&#xff1a;非妃是公主 专栏&#xff1a;《计算机图形学》 博客地址&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录专栏推荐专栏系列文章序一、算法原理二、…

springboot整合mybatis框架,简单实现CRUD

如果大家实在不知道怎么搞可以去看看官网:mybatis-plus官网MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。其实也就是在…

5. 驱动开发

文章目录一、驱动开发1.1 前言1.2 何谓驱动框架1.3 内核驱动框架中LED的基本情况1.3.1 相关文件1.3.2 九鼎移植的内核中led驱动1.3.3 案例分析驱动框架的使用1.3.4 典型的驱动开发行业现状1.4 初步分析led驱动框架源码1.4.1 涉及到的文件1.4.2 subsys_initcall1.4.3 led_class_…

windows应用(vc++2022)MFC基础到实战(1)

目录vc概述MFC 框架概述MFC 框架SDI 和 MDI文档、视图和框架窗口对象文档/视图体系结构第一个应用自动生成的主框架类源码vc概述 Microsoft Visual C&#xff08;简称Visual C、MSVC、VS或VC&#xff09;是微软公司的免费C开发工具&#xff0c;具有集成开发环境&#xff0c;可…