本文由miaoYu在众成翻译平台翻译。
最近做了一个花瓣从树上飘落的动画,树是通过随机算法生成的(主要是受到这个Demo的启发),然后在树枝的末端随机生成小花。
这个动画我用了Promises来帮我实现。这将涉及到异步和递归。
从小小的种子。。。
第一步,使用递归随机生成一颗树,以下是一些关键的点:
从画树干开始——树干的的起点(地面),树干的终点(树干离地面的高度) .
画树干。
接下来画一些树枝——它们从树干的末端开始画,我们分别选择左右两个随机角度来延伸树枝,树枝的长度是比树干稍短的随机长度,这样就可以计算出新树枝的长度。
画树枝。
对于这两条新的树枝,我们获取到它们的末端位置,然后再长出两条新的树枝,就像第3步那样。
重复第5步,直到生成一颗茂密的大树!
递归算法可以生成一棵树,但是我们如何模拟一棵树缓慢生长的动画呢?ok,我们可以加延迟!
在第3步到第4步之间,画新树枝的时候,我们可以延迟(使用setTimeout)。由于重复在画新的分支,树将缓慢的呈扇形生长,计算,等待,生成,计算,等待,生成。。。
所以我来描述下我们即将要完成的代码(未定义的变量只是用于描述):
// 树枝按照指定的初始点,长度,角度被渲染生成。
function growBranch({ startingPoint, length, angle, remainingBranches }) {
// 通过computeEndpoint方法来获取到树枝结束点
endingPoint = computeEndpoint({ startingPoint, length, angle });
// 渲染树枝
renderBranch({ startingPoint, endingPoint });
// 计算出剩下需要渲染的树枝,每次减一,当为0时,结束渲染
const newRemainingBranches = remainingBranches - 1;
if (newRemainingBranches <= 0) {
return;
}
// 随机的生成左右两条树枝
["left", "right"].forEach(direction => {
const newAngle = computeRandomAngle(direction);
const newLength = shrinkBranchLength(length);
// 递归异步
setTimeout(function() {
growBranch({
startingPoint: endingPoint,
length: newLength,
angle: newAngle,
remainingBranches: newRemainingBranches
});
}, DELAY);
});
}
// 通过调用生成树枝方法来生成树
function growTree() {
growBranch({
startingPoint: GROUND, // 从地面开始生长
length: TRUNK_LENGTH, // 初始化长度
angle: STRAIGHT_UP, // 第一次生成的是主干,所以是90度
remainingBranches: TREE_COMPLEXITY // 最多生成多少级的树枝
});
}
一旦树画好了,我们就知道了最后生成的树枝,下个阶段我们在这些树枝的末端画花。具体来说,当我意识到newRemainingBranches这个变量小于等于0时候,意味着不会产生新的树枝了,因此我们可以知道所有最后生成树枝的末端位置。
因为我们加了延迟,所以我们需要知道什么时候所有末端树枝能渲染完。
如果所有树枝画好后,能通知我们就好了。
Promises是实现上述要求的最好方法!我们将得到最后生成树枝末端位置的数组,我们在这些位置上画花。
具体来说就是当growTree()调用growBranch()时候,growBranch()将会递归调用自己,在growBranch()方法中,判断newRemainingBranches <= 0时(也就是不再产生新树枝时),将末端位置作为resolve()方法的参数汇集成一个数组。
// 树枝按照指定的初始点,长度,角度被渲染生成。
function growBranch({ startingPoint, length, angle, remainingBranches }) {
endingPoint = computeEndpoint({ startingPoint, length, angle });
renderBranch({ startingPoint, endingPoint });
const newRemainingBranches = remainingBranches - 1;
if (newRemainingBranches <= 0) {
// 返回一个包含树枝的末端位置参数的回调
return Promise.resolve(endingPoint);
}
// 使用Promise.all方法来保证,一旦左右两个树枝完成,就返回末端位置。
return Promise.all(
["left", "right"].map(direction => {
const newAngle = computeRandomAngle(direction);
const newLength = shrinkBranchLength(length);
// 包裹setTimeout,递归调用
return new Promise(resolve => {
setTimeout(function() {
resolve(
growBranch({
startingPoint: endingPoint,
length: newLength,
angle: newAngle,
remainingBranches: newRemainingBranches
})
);
}, DELAY);
});
})
// 得到最后生成树枝末端位置的数组
).then(flatten);
}
// 通过调用生成树枝方法来生成树
function growTree() {
return growBranch({
startingPoint: GROUND,
length: TRUNK_LENGTH,
angle: STRAIGHT_UP,
remainingBranches: TREE_COMPLEXITY
});
}
下面是一些Promise对象的方法:
Promise.resolve()是一个很有实用方法,在异步操作成功后调用,并将参数传递出去。
Promise.all() 另外一个很实用的方法,它的参数是一个promise对象数组,当数组里所有函数执行完毕,然后打包返回。
new Promise() Mozilla简洁深入的解释.
Promise.prototype.then() 链式调用结束后调用
下面是生成一棵树的完整代码:
<iframe height='483' scrolling='no' title='Tree' src='//codepen.io/goldEli/embed/WOxKev/?height=483&theme-id=light&default-tab=result&embed-version=2' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;'>See the Pen Tree by miaoyu (@goldEli) on CodePen. </iframe>
运行以上代码后,可看到console打印出了一个包含所有树枝末端位置的数组。还有你会发现和之前的代码有很大的不同,是因为之前的代码只是用于梳理逻辑的。
最后,我们就可以画花了,以下是整个demo的完整代码:
<iframe height='580' scrolling='no' title='Hanami' src='//codepen.io/goldEli/embed/pwbZJE/?height=580&theme-id=light&default-tab=result&embed-version=2' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;'>See the Pen Hanami by miaoyu (@goldEli) on CodePen. </iframe>
如果你也用到了类似的技术来解决问题,请在我的twitter @OngEmil 告诉我???
原文来自:众成翻译
声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com
支持识别各类商场、超市及药店的购物小票,包括店名、单号、总金额、消费时间、明细商品名称、单价、数量、金额等信息,可用于商品售卖信息统计、购物中心用户积分兑换及企业内部报销等场景
涉农贷款地址识别,支持对私和对公两种方式。输入地址的行政区划越完整,识别准确度越高。
根据给定的手机号、姓名、身份证、人像图片核验是否一致
通过企业关键词查询企业涉讼详情,如裁判文书、开庭公告、执行公告、失信公告、案件流程等等。
IP反查域名是通过IP查询相关联的域名信息的功能,它提供IP地址历史上绑定过的域名信息。