以前就看到了这个东西,由于太忙了最近才有时间来实现这个;

该文章适合有一定 canvas 基础的人阅读;

首先说说他的原理:

The construction of the Pythagoras tree begins with a square. Upon this square are constructed two squares, each scaled down by a linear factor of ½√2, such that the corners of the squares coincide pairwise. The same procedure is then applied recursively to the two smaller squares, ad infinitum. The illustration below shows the first few iterations in the construction process.

Order 0 Order 1 Order 2 Order 3

以上文字和图像摘自维基百科:https://en.wikipedia.org/wiki/Pythagoras_tree_(fractal)

大意是先有一个正方形,然后在他的上方构建2个正方形,使他们三条边构成一个三角形,再向上蔓延,循环往复,直至无穷;

当然我们是不可能做无穷的,这样很容易崩溃的;

先写上代码,我做了比较详细的注释:

首先制作背景及一些条件的确定:

var Init = function() {
var canvas = document.getElementById('canvas');
this.ctx = canvas.getContext('2d'); this.vw = canvas.width = window.innerWidth;
this.vh = canvas.height = window.innerHeight; //定义 canvas大小
this.boxSize = 80; //树的大小 this.maxLevel = 6; //树的节数
this.color1 = { h: 70, s: 75, l: 51 }; //树顶颜色 青绿色
this.color2 = { h: 310, s: 98, l: 17 }; //树根颜色 咖啡色
// HSL色彩模式:就是色调(Hue)、饱和度(Saturation)、亮度(Lightness)三个颜色通道的改变以及它们相互之间的叠加来获得各种颜色,色调(Hue)色调最大值360,饱和度和亮度有百分比表示0-100%之间。
// H:
// Hue(色调)。0(或360)表示红色,120表示绿色,240表示蓝色,也可取其他数值来指定颜色。取值为:0 - 360
// S:
// Saturation(饱和度)。取值为:0.0% - 100.0%
// L:
// Lightness(亮度)。取值为:0.0% - 100.0%
// 当然这里 rgb 肯定也是可以的 this.mouse = {
x: this.vw / 2,
y: this.vh / 2
}; //默认鼠标位置 屏幕中心 this.lean = 0; //三角形顶点左右偏移程度
this.scale = 0; //三角形高度比例 this.x = (this.vw - this.boxSize) / 2;
//x 时候从浏览器左边开始到树根左边的长度 同样的也是 浏览器右边到树根右边的长度
this.y = this.vh; this.getColors(this.color1, this.color2, this.maxLevel + 1);
//取色,将颜色从 color1到 color 2 平均分成 maxLevel+1 种,你可以加3,加4 因为 maxLeval 不足的话有一部分颜色计算出来是白色会与白色背景冲突
}

上部有 getColor 函数,现在我们来定义他,他的作用是返回一个用来存储颜色的数组,颜色是根据上述的 color1和 color2 还有this.maxlevel 来确定的;

循环 maxlevel 来分离出这两种颜色之间的不同色差;

Init.prototype.getColors = function(c1, c2, steps) {
this.colors = [];
var lerp = this.methods.lerp;
for (let i = 0; i < steps; i++) {
const t = i / (steps - 1);
const h = Math.round(lerp(c1.h, c2.h, t));
const s = Math.round(lerp(c1.s, c2.s, t));
const l = Math.round(lerp(c1.l, c2.l, t)); this.colors.push('hsl('+h+','+s+'%,'+l+'%)');
}
}

再是绘制出树根的代码,他是根据 size ,scale,lean,level 即,正方形的边长,构成三角形的高的比例,三角形的顶点的左右偏移,还有 level 即上部代码的颜色等级;

他的执行顺序是先绘制左边的正方形,在绘制右边的;

Init.prototype.drawTree = function(size, scale, lean, level) {
//最初值80 0.4 0 5
//长度,弯曲度,倾斜度,颜色域
var ctx = this.ctx;
var constitute = this.calcBranches(size, scale, lean);
//获取构成三角形的边长和角度 ctx.save(); //因为倾斜的角度是不一样的,所以需要存储
ctx.fillRect(0, 0, size, -size); //构成三角形的绘出正方形,初始是树根的位置 ctx.fillStyle = this.colors[level]; //填充树的颜色 ctx.translate(0, -size); //改变canvas的坐标位置 ,移至正方形左上角
ctx.rotate(-constitute.leftAngle); //根据一个角度对图像进行旋转,负值代表向左倾斜 if (level) {
this.drawTree(constitute.leftSize, scale, lean, level - 1);
} else {
ctx.fillRect(0, 0, constitute.leftSize, -constitute.leftSize);
//最后一种颜色,递归结束
}
//根据颜色域,来递归进行渲染三角形 直至颜色用完 ctx.translate(constitute.leftSize, 0); //改变坐标 横坐标移至左边正方形的右下角
ctx.rotate(constitute.rightAngle + constitute.leftAngle); //旋转
//渲染右边的正方形,rotate 是整个绘图都会旋转,因为刚刚 rotate(-leftAngle),所以加上 leftAngle 是让整个图像变成水平,再根据 rightAngle 进行右旋转
if (level) {
this.drawTree(constitute.rightSize, scale, lean, level - 1);
} else {
ctx.fillRect(0, 0,constitute.rightSize, -constitute.rightSize);
} //递归绘制右正方形 ctx.restore();
}

上述代码中有一个函数为calcBranches 这是最为关键的代码,他的只要作用是通过计算得出下一个绘制的正方形的边长,角度,顺便将他们存储起来,减缓浏览器的压力;

关于缓存,是通过闭包来实现的:

一个简单的例子就是:

var fun1 = (function (){
var arr = [];
var fun2 = function(){
arr.push( (new Date()).valueOf() )
}
return fun2;
})()

这段代码执行的时候 fun1 当即会执行一遍,这样就会出现一个 arr 变量,但是这个变量并不会暴露出来;

但是而每当调用fun1(fun2)时便会对 arr 作出改变,这便是缓存的原理;

calcBranches代码:

Init.prototype.calcBranches = (function() {
var cache = {}; var memoize = function(width, scale, lean) {
//长度,三角形高度比例,倾斜度 初始值 80 0.4 0
//lean 范围 -0.5 到0.5 lean接近0.5时 左树枝几乎为0 同理 -.5时 有树枝几乎为0 即构成三角形皆为直角三角形
var memoKey = width+'-'+scale+'-'+lean;
if (!cache[memoKey]) {
//存储这三个值,形成缓存,减少绘制压力
var currentH = width * scale; //当前高度即构成三角形对于正方形的高度 var result = {
leftSize: Math.sqrt(currentH ** 2 + (width * (0.5 - lean)) ** 2),
rightSize: Math.sqrt(currentH ** 2 + (width * (0.5 + lean)) ** 2),
leftAngle: Math.atan(currentH / ((0.5 - lean) * width)),
rightAngle: Math.atan(currentH / ((0.5 + lean) * width))
};
//**表示几次方
//关键代码,根据当前的长度
//初始化时 第一个width = boxSize;
//leftSize 构成三角形的左边长
//rightSize 右边长
//leftAngle 左边的角度的反正切值 -PI/2 到 PI/2 之间的弧度值。 是一个角度
//rightAngle 右边的角度的反正切值 同上
cache[memoKey] = result;
}
return cache[memoKey]; }
memoize.cache = cache;
return memoize;
})();//通过闭包实现缓存;

再就是渲染函数,他的主要作用就是绘制初始图形,并且通过计算得出需要绘制的正方形的 scale,lean 这2个关键影响因素;

他们之间的关系你可以自己随便改;

Init.prototype.render = function(){
var map = this.methods.map,
ctx = this.ctx
var scale = map(this.mouse.y, this.vh, 0, 0, 0.8)
//通过 map函数得出高的比例 主要来源鼠标 y 的值 var lean = map(this.mouse.x, 0,this.vw, 0.5, -0.5)
//通过 map函数得出左右倾斜程度 只要影响:鼠标 x 的值 ctx.clearRect(0,0,this.vw,this.vh)//清空画布 ctx.save(); //因为颜色会渐变,所以需要使用 save 存储为染色前的状态
ctx.fillStyle = this.colors[this.maxLevel];
//树根颜色
ctx.translate(this.x,this.y); //将 canvas 坐标移动到 x,0 x 是树根左边距浏览器左边的长度 this.drawTree(this.boxSize, scale, lean,this.maxLevel); //绘制初始树 ctx.restore(); requestAnimationFrame(this.render.bind(this));
}

他们之间所用的函数, lerp,map都是纯函数,即当输入的数是不变的情况下,不管输入几次他的输出都是固定的,即没有随机数的影响,比如时间戳等等;

Init.prototype.methods = {
//pure function
lerp: function lerp(a, b, t) {
return a + (b - a) * t;
},
map:function map(x, a, b, c, d) {
return c + (d - c) * ((x - a) / (b - a)) || 0;
}
}

最后一步, new 出对象,进行 render ,再添加监听鼠标移动事件:

var init = new Init();
init.render();
window.addEventListener("mousemove", function(event) {
init.mouse.x = event.clientX;
init.mouse.y = event.clientY; });

基本过程便是这样;

部分代码我也是从网上获取灵感;

我的 demo 中

这个是最基本的构成;最底下的我取名树根;在上部是树枝;

再是:

该红色的高就是根据鼠标的高度来生成的即代码中的currentH;

  

关于该红色的点的定位主要由鼠标位置和网页大小组成的;代码中的scale决定该点的上下高度(主要受鼠标纵坐标影响),即上面所说的高,而 lean 决定该点的左右坐标(主要受鼠标横坐标影响);

demo地址:https://grewer.github.io/JsDemo/pythagorasTree/pythagorasTree.html

github:https://github.com/Grewer/JsDemo/blob/master/pythagorasTree/pythagorasTree.html

希望大家能给个推荐或 star ,十分感谢!

完;

毕达哥拉斯树(pythagorasTree)原理解析及canvas动画实现的更多相关文章

  1. View 动画 Animation 运行原理解析

    这次想来梳理一下 View 动画也就是补间动画(ScaleAnimation, AlphaAnimation, TranslationAnimation...)这些动画运行的流程解析.内容并不会去分析 ...

  2. Dojo动画原理解析

    dojo中动画部分分为两部分:dojo/_base/fx, dojo/fx.dojo/_base/fx部分是dojo动画的基石,里面有两个底层API:animateProperty.anim和两个常用 ...

  3. HTML5 Canvas动画效果实现原理

    在线演示 使用HTML5画布可以帮助我们高速实现简单的动画效果.基本原理例如以下: 每隔一定时间绘制图形而且清除图形,用来模拟出一个动画过程,能够使用context.clearRect(0, 0, x ...

  4. 标准Trie字典树学习一:原理解析

    特别声明: 博文主要是学习过程中的知识整理,以便之后的查阅回顾.部分内容来源于网络(如有摘录未标注请指出).内容如有差错,也欢迎指正! 系列文章: 1. 字典树Trie学习一:原理解析 2.字典树Tr ...

  5. View Animation 运行原理解析

    Android 平台目前提供了两大类动画,在 Android 3.0 之前,一大类是 View Animation,包括 Tween animation(补间动画),Frame animation(帧 ...

  6. APPcrawler基础原理解析及使用

    一.背景 一年前,我们一直在用monkey进行Android 的稳定性测试 ,主要目的就是为了测试app 是否会产生Crash,是否会有ANR,页面错误等问题,在monkey测试过程中,实现了脱离Ca ...

  7. 《每周一点canvas动画》——3D点线与水波动画

    <每周一点canvas动画>--差分函数的妙用 每周一点canvas动画代码文件 好像上次更新还是十一前,这唰唰唰的就过去大半个月了,现在才更新实在不好意思.这次我们不涉及canvas 3 ...

  8. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  9. 2015.4.23 贪吃蛇、canvas动画,各种上传工具,url信息匹配以及最全前端面试题等

    1.面向对象贪吃蛇   2.css中:hover 改变图片 页面加载完 第一次鼠标移入会闪一下 这是为啥? 解决方法:你把两张图合成一张图或者是先把图片加载到页面上,然后再hover出来. 解析:图片 ...

随机推荐

  1. Problem L

    Problem Description 在2×n的一个长方形方格中,用一个1× 2的骨牌铺满方格,输入n ,输出铺放方案的总数. 例如n=3时,为2× 3方格,骨牌的铺放方案有三种,如下图: L&qu ...

  2. FTP&samba 服务简单部署

    第1章 FTP服务部署 在Linux下,我们应用最广泛的FTP服务程序是 vsftpd ( TCP端口:而NMB服务是负责解析用的,类似与DNS实现的功能,NMB可以把Linux系统共享的工作组名称与 ...

  3. Android模仿iOS iMessages10照片选择器的实现

    不知不觉已经接近半年多没有写过博客了,这段时间,也是我刚好毕业走出校园的时间,由于学习工作的原因,一直没有真正静下心来写下些什么东西.这个星期刚入了小米笔记本pro的坑,本着新电脑新生活的理念嘻嘻-- ...

  4. AngularJS学习篇(十八)

    AngularJS API AngularJS 全局 API 用于执行常见任务的 JavaScript 函数集合,如: 比较对象 迭代对象 转换对象 全局 API 函数使用 angular 对象进行访 ...

  5. 常用meta整理[转载]

    < meta > 元素 概要 标签提供关于HTML文档的元数据.元数据不会显示在页面上,但是对于机器是可读的.它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他web ...

  6. Python实现翻译功能

    初入Python,一开始就被她简介的语法所吸引,代码简洁优雅,之前在C#里面打开文件写入文件等操作相比Python复杂多了,而Python打开.修改和保存文件显得简单得多. 1.打开文件的例子: fi ...

  7. php多个文件上传

    表单如下 <form class="form-horizontal" action="{:U('System/addAdvert')}" method=& ...

  8. [转载] OAuth2.0认证和授权原理

    转载自http://www.tuicool.com/articles/qqeuE3 什么是OAuth授权? 一.什么是OAuth协议 OAuth(开放授权)是一个开放标准,允许第三方网站在用户授权的前 ...

  9. [转载] 使用Redis的Java客户端Jedis

    转载自http://aofengblog.blog.163.com/blog/static/631702120147298317919/ 在实际的项目开发中,各种语言是使用Redis的客户端库来与Re ...

  10. Golang源码探索(三) GC的实现原理

    Golang从1.5开始引入了三色GC, 经过多次改进, 当前的1.9版本的GC停顿时间已经可以做到极短. 停顿时间的减少意味着"最大响应时间"的缩短, 这也让go更适合编写网络服 ...