canvas加三角函数写一个动画

前言

在掘金上看到一个帖子https://w3ctrain.com/2018/08/20/trigonometry-you-must-know/
运用canvas和三角函数写了一个动画 好厉害的感觉 所以赶紧学习一下
先看效果

upload_45025fcd1837ff31048db203afa206c1-sadman-sine.gif

首先绘制头部

    drawHead(t) {
                ctx.save()
                ctx.beginPath()
                ctx.translate(0, Math.sin(t) * 4)
                ctx.arc(80, -35, 35, 0, 2 * Math.PI)
                ctx.fill()
                ctx.closePath()
                ctx.restore()
            }

我会给每个方法传入周期参数 t, t 从 0 到 2 PI , 这样能保证所有的周期运动时间同步。

head.gif

身体和阴影的绘制都差不多,直接跳过看脚步动画。

脚有两只,按道理应该是抬脚到落脚的动作完成时,其他部位都完成了一个完整的周期,所以在绘制脚的时候,t 需要除以 2。然后第一只脚和第二只脚相差半个脚自身的周期,可以直接将 t 替换成 t + Math.PI 就是第二脚的动画。

drawFeet (t) {
t = t / 2
  ctx.translate(Math.cos(t) * -50, 0)

  // 另一只脚
  ctx.translate(Math.cos(t + Math.PI) * -50, 0)
}
foot1.gif

脚步动画自身周期的一半是在地面上的,可以通过判断一下 sin 值,小于 0 则不做 y 纵轴方向上的变化。

ctx.translate(Math.cos(t) * -50, Math.sin(t) > 0 ? Math.sin(t) * -35 : 0)
foot2.gif

还没完,为了让脚更加逼真,同样在前半个周期做一下 rotate 。

if (t < Math.PI) {
  ctx.rotate(Math.sin(t) * Math.PI / 180 * -5)
}

foot3.gif

最终得到的效果是这样的:
upload_45025fcd1837ff31048db203afa206c1-sadman-sine.gif

文章参考:https://w3ctrain.com/2018/08/20/trigonometry-you-must-know/

//完整代码如下
<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title></title>
        <style type="text/css">
            html,
            body {
                padding: 0;
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
            
            canvas {
                width: 100%;
                height: 100%;
                background: #f1513b;
            }
        </style>
    </head>

    <body>
        <!--<canvas id="ctx" width="" height=""></canvas>-->
    </body>
    <script type="text/javascript">
        // inspired by the pure css sad man https://codepen.io/knyttneve/pen/yqwXyM
        const canvas = {
            init() {
                this.ele = document.createElement('canvas')
                document.body.appendChild(this.ele)
                this.resize()
                window.addEventListener('resize', () => this.resize(), false)//resize:widow窗口大小发生变化的函数
                this.ctx = this.ele.getContext('2d')
                return this.ctx
            },
            onResize(callback) {
                this.resizeCallback = callback
            },
            resize() {
                this.width = this.ele.width = window.innerWidth * 2
                this.height = this.ele.height = window.innerHeight * 2
                this.ele.style.width = this.ele.width * 0.5 + 'px'
                this.ele.style.height = this.ele.height * 0.5 + 'px'
                this.ctx = this.ele.getContext('2d')
                this.ctx.scale(2, 2)
                this.resizeCallback && this.resizeCallback()
            },
            run(callback) {
                requestAnimationFrame(() => {
                    this.run(callback)
                })
                callback(this.ctx)
            }
        }

        const ctx = canvas.init()

        let objects = []

        class SadMan {
            drawHead(t) {
                ctx.save()
                ctx.beginPath()
                ctx.translate(0, Math.sin(t) * 4)
                ctx.arc(80, -35, 35, 0, 2 * Math.PI)
                ctx.fill()
                ctx.closePath()
                ctx.restore()
            }
            drawBody(t) {
                ctx.beginPath()
                ctx.save()
                ctx.rotate(Math.sin(t) * Math.PI / 180 * -1)
                ctx.translate(0, Math.sin(t) * 4)
                ctx.scale(0.5, 0.5)
                const body = new Path2D('M125,284 L1,284 C0.33333333,94.6666667 35,0 105,0 C115.666667,4 122.333333,20.6666667 125,50 L125,284 Z')
                ctx.fill(body)
                ctx.restore()
                ctx.closePath()
            }
            drawFeet(t) {
                t = t / 2
                ctx.save()
                ctx.scale(0.5, 0.5)
                ctx.translate(0, 460)
                const foot = new Path2D('M23,0 C67,0 80,16 80,22 C80,26 78.6666667,28 76,28 C29.3333333,28 6,28 6,28 C6,28 -1.34111707e-14,30 0,17 C1.42108547e-14,4 10,1.9505735e-16 13,0 C16,0 13,0 23,0 Z')

                ctx.save()
                ctx.translate(Math.cos(t) * -50, Math.sin(t) > 0 ? Math.sin(t) * -35 : 0)
                if(t < Math.PI) {
                    ctx.rotate(Math.sin(t) * Math.PI / 180 * -5)
                }
                ctx.fill(foot)
                ctx.restore()

                ctx.save()
                ctx.translate(Math.cos(t + Math.PI) * -50, Math.sin(t + Math.PI) > 0 ? Math.sin(t + Math.PI) * -35 : 0)
                if(t > Math.PI) {
                    ctx.rotate(Math.sin(t + Math.PI) * Math.PI / 180 * -5)
                }
                ctx.fill(foot)
                ctx.restore()

                ctx.restore()
            }
            drawShadow(t) {
                ctx.beginPath()
                ctx.save()
                ctx.scale(0.5, 0.5)
                ctx.translate(45, 490)
                ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'
                ctx.ellipse(0, 0, 120 + Math.sin(t) * 10, 8, 0, 0, 2 * Math.PI)
                ctx.fill()
                ctx.restore()
                ctx.closePath()
            }
            draw(t) {
                t = t % Math.PI * 2
                ctx.fillStyle = 'white'
                ctx.save()
                ctx.translate(window.innerWidth * 0.5 - 140, window.innerHeight * 0.5 - 80)
                this.drawShadow(t)
                this.drawHead(t)
                this.drawBody(t)
                this.drawFeet(t)
                ctx.restore()
            }
        }

        const init = () => {
            objects = []
            objects.push(new SadMan())
            // objects.push(new Feet())
        }

        document.addEventListener('click', () => {
            init()
        })

        init()

        let tick = 0
        canvas.run(ctx => {
            ctx.clearRect(0, 0, canvas.width, canvas.height)
            tick += 0.03
            objects.forEach(obj => {
                obj.draw(tick)
            })
        })

        canvas.onResize(() => {
            init()
        })
    </script>

</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容