最近在学习 canvas , 于是就把之前绘制的一套正义联盟的 UI 拿来用, 做了一个基于 canvas 的小动画. 也明白了一个之前的误区: canvas 本身是没有绘图和动画能力的, 还是需要借助 JavaScript 完成.
下面进入正题, 先甩上 Demo 和下载地址:
Demo: 听说, 正义联盟要造反 https://littleyljy.github.io/demo/workstory/
下载地址: https://github.com/littleyljy/workstory
html 结构
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
- <meta name="author" content="Author">
- <title > 听说, 正义联盟要造反</title>
- <link href="index.css" rel="stylesheet" type="text/css" />
- </head>
- <body>
- <section id="story" class="story">
- <p class="story-title">太阳当空照, 花儿对我笑.</p>
- <p class="story-content">老板说: 早早早, 今天晚上继续加个班.</p>
- </section>
- <canvas id="main" class="main"></canvas>
- <section class="package">
- <div class="start"><img src="img/startBtn2.png"></div>
- <div class="boss"><img src="img/boss.png"></div>
- <div class="staff"><img src="img/staff.png"></div>
- <div class="question"><img src="img/question.png"></div>
- <div class="skull"><img src="img/skull.png"></div>
- <div class="heart"><img src="img/heart.png"></div>
- <div class="over"><img src="img/shareBtn.png"></div>
- </section>
- <section class="share-layer">
点击右上角, 让朋友们也泄泄愤吧~
- </section>
- <audio id="bg-music" src="audio/start.mp3" autoplay preload loop="loop"></audio>
- <script src="jquery.min.js"></script>
- <script src="config.js"></script>
- <script src="index.js"></script>
- </body>
- </html>
其实结构不复杂, 主要分为五块:
1, 文字区 .story
2,canvas 画布 .main
3, 组件区 .package
4, 分享区 .share-layer
5, 背景音乐 #bg-music
配置文件
动画分为四个场景, 以正义联盟的四个英雄为主角: 蝙蝠侠, 超人, 神奇女侠, 闪电侠.
在 config.js 文件中定义好每个场景故事内容和角色对象.
- var stories= [{
- title:'老板: 蝠蝠, 加个班吧!',
- content:'蝙蝠侠: 不加, 老子有钱任性.'
- }, {
- title:'老板: 超超, 加个班吧!',
- content:'超人: 你被炒了, 现在我是老板.'
- }, {
- title:'老板: 美奇, 加个班吧!',
- content:'神奇女侠: 我加班的小心心留给你.'
- }, {
- title:'老板: 小电, 加个班吧!',
- content:'闪电侠: 老板, 再见!'
- }, {
- }];
- // 绘制蝙蝠侠对象
- var batmanImg = new Image();
- batmanImg.src = 'img/batman.png';
- var batman = {
- x: 50,
- y: 500,
- vy: 4,
- w: 75,
- h: 100,
- au: 'audio/fall.mp3',
- direction: 'start'
- }
- // 绘制老板对象
- var bossImg = new Image();
- bossImg.src = 'img/boss.png';
- var boss = {
- x: 240,
- y: 500,
- vy: 4,
- w: 75,
- h: 100,
- direction: 'start'
- }
- // 更多角色代码在此省略......
场景一: 蝙蝠侠
蝙蝠侠场景演示
先来说一下打字机效果.
$.fn.autotype = function() {}; 可以理解为为 jquery 对象定义了一个 autotype 方法.
- // 打字效果方法
- $.fn.autotype = function() {
- var $text = $(this);
- console.log('this', this);
- var str = $text.html(); // 返回被选 元素的内容
- var index = 0;
- var x = $text.html('');
- //$text.html()和 $(this).html('')有区别
- var timer = setInterval(function() {
- //substr(index, 1) 方法在字符串中抽取从 index 下标开始的一个的字符
- var current = str.substr(index, 1);
- if (current == '<') {
- //indexOf() 方法返回 ">" 在字符串中首次出现的位置.
- index = str.indexOf('>', index) + 1;
- } else {
- index++;
- }
- //console.log(["0 到 index 下标下的字符",str.substring(0, index)],["符号",index & 1 ? '_': '']);
- //substring() 方法用于提取字符串中介于两个指定下标之间的字符
- $text.html(str.substring(0, index) + (index & 1 ? '_': ''));
- if (index>= str.length) {
- clearInterval(timer);
- }
- },
- 100);
- };
然后定义一个 autotypeinit 函数, 先清空 .story-title 和 .story-content 中的文字, 然后调用 .autotype() 方法产生打字效果. 这里 setTimeout 函数是为了先把第一句话打完, 再打第二句话.
- // 调用打字方法
- function autotypeinit(i){
- $('.story-content').html('');
- $('.story-title').html('');
- $('.story-title').html(stories[i].title);
- $('.story-title').autotype();
- setTimeout(function(){
- $('.story-content').html(stories[i].content);
- $('.story-content').autotype();
- },1000);
- }
canvas 实现动画的原理就是不断绘制图形, 改变图形(形状, 位移等), 清除画布的过程.
- // 蝙蝠侠场景
- function dropBoss(){
- console.log('chest.y',chest.y);
- // 宝箱下落
- if (chest.y <425) {
- chest.y += chest.vy;
- }else{
- if(boss.h> 25){
- boss.h -= boss.vy;
- boss.y += boss.vy;
- chest.y += chest.vy;
- }else{
- if(batman.y> -100){
- batman.y -= batman.vy;
- }else{
- count = 1;
- autotypeinit(count);
- chgBoss();
- return;
- }
- }
- }
- // 清除画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // 重绘
- ctx.drawImage(batmanImg,batman.x,batman.y,batman.w,batman.h);
- ctx.drawImage(bossImg,boss.x,boss.y,boss.w,boss.h);
- ctx.drawImage(chestImg,chest.x,chest.y,chest.w,chest.h);
- // 使用 requestAnimationFrame 实现动画循环
- requestAnimationFrame(dropBoss);
- }
- // 超人场景
- function chgBoss(){
- console.log('sunglass.y',sunglass.y);
- $('.boss').show();
- // 移动墨镜
- if (sunglass.y <520) {
- sunglass.y += sunglass.vy;
- }else{
- $('.boss').hide();
- $('.staff').show();
- $('.question').show();
- count = 2;
- setTimeout(function(){
- autotypeinit(count);
- koBoss();
- },2000);
- return;
- }
- // 清除画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // 重绘
- ctx.drawImage(supermanImg,superman.x,superman.y,superman.w,superman.h);
- ctx.drawImage(sunglassImg,sunglass.x,sunglass.y,sunglass.w,sunglass.h);
- // 使用 requestAnimationFrame 实现动画循环
- requestAnimationFrame(chgBoss);
- }
- /* 角色 */
- .boss{
- display: none;
- position: absolute;
- top: 500px;
- left: 240px;
- z-index:1;
- }
- .staff{
- display: none;
- position: absolute;
- top: 500px;
- left: 240px;
- z-index: 10;
- -webkit-animation: rotateOutUpRight 1s 1s 1;
- animation: rotateOutUpRight 1s 1s 1;
- }
- .question{
- display: none;
- position: absolute;
- top: 450px;
- left: 270px;
- z-index: 10;
- -webkit-animation: flash 2s infinite;
- animation: flash 2s infinite;
- }
- @-webkit-keyframes rotateOutUpRight{
- 0%{
- transform-origin:right bottom;
- opacity:1
- }
- 0%,to{
- -webkit-transform-origin:right bottom
- }
- to{
- transform-origin:right bottom;
- -webkit-transform:rotate(90deg);
- transform:rotate(90deg);
- opacity:0
- }
- }
- @keyframes rotateOutUpRight{
- 0%{
- transform-origin:right bottom;
- opacity:1
- }
- 0%,to{
- -webkit-transform-origin:right bottom
- }
- to{
- transform-origin:right bottom;
- -webkit-transform:rotate(90deg);
- transform:rotate(90deg);
- opacity:0
- }
- }
- @-webkit-keyframes flash{
- 0%,50%,to{
- opacity:1;
- }
- 25%,75%{
- opacity:0;
- }
- }
- @keyframes flash{
- 0%,50%,to{
- opacity:1;
- }
- 25%,75%{
- opacity:0;
- }
- }
- // 平移物体至原点位置
- ctx.translate(300,200);
- // 以新原点为中心旋转 60°
- ctx.rotate(Math.PI / 3);
- // 神奇女侠场景
- function koBoss(){
- console.log('wonderwoman.y',wonderwoman.y);
- $('.boss').show();
- $('.staff').hide();
- $('.question').hide();
- // 移动小人
- if (wonderwoman.x> -75) {
- wonderwoman.x -= wonderwoman.vy;
- $('.heart').show();
- }else{
- count = 3;
- autotypeinit(count);
- flashBoss();
- return;
- }
- // 清除画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // 重绘
- ctx.drawImage(wonderwomanImg,wonderwoman.x,wonderwoman.y,wonderwoman.w,wonderwoman.h);
- // 使用 requestAnimationFrame 实现动画循环
- requestAnimationFrame(koBoss);
- }
- .heart{
- display: none;
- position: absolute;
- top: 550px;
- left: 100px;
- z-index: 10;
- -webkit-animation: rotate 4.5s 1s 1;
- animation: rotate 4.5s 1s 1;
- }
- @-webkit-keyframes rotate{
- 0%{
- transform-origin:right bottom;
- }
- 0%,to{
- -webkit-transform-origin:right bottom
- }
- to{
- transform-origin:right bottom;
- -webkit-transform:rotate(90deg);
- transform:rotate(90deg);
- }
- }
- @keyframes rotate{
- 0%{
- transform-origin:right bottom;
- }
- 0%,to{
- -webkit-transform-origin:right bottom
- }
- to{
- transform-origin:right bottom;
- -webkit-transform:rotate(90deg);
- transform:rotate(90deg);
- }
- }
- // 闪电侠场景
- function flashBoss(){
- console.log('flash.y',flash.y);
- $('.heart').hide();
- if (flash.y> -100) {
- flash.y -= flash.vy;
- flash.x = parseInt(Math.random()*(max-min+1)+min,10);
- $('.skull').show();
- }else{
- $('.over').show();
- return;
- }
- // 清除画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // 重绘
- ctx.drawImage(flashImg,flash.x,flash.y,flash.w,flash.h);
- // 使用 requestAnimationFrame 实现动画循环
- requestAnimationFrame(flashBoss);
- }
- .skull{
- display: none;
- position: absolute;
- top: 475px;
- left: 220px;
- z-index: 10;
- -webkit-animation: flash 0.5s infinite;
- animation: flash 0.5s infinite;
- }
来源: http://www.jianshu.com/p/542d8503188b