送给前端工程师的生日礼物
❝前言:哈喽,我是树酱,今天跟大家分享一篇来自寒草的沙雕文章 并祝他生日快乐。 关于寒草😈的自我介绍:一只草系码猿🐒。间歇性热血🔥,持续性沙雕🌟。
❞
前奏 🎵
文章概述
本篇文章,大家可以:
与我一同设计一款生日礼物 🎁 与我携手一同完成生日礼物 🎁 的开发 本人生日 🎂 将至的有感而发
希望大家阅读本篇文章,可以体验到:
前端的乐趣 制作本礼物用到的css基础知识 从我的有感而发中获得一些经验或者感悟
当然我毕竟生日 🎂 将至,所以本篇文章十分期待大家的「生日祝福」,哈哈哈,求伙伴们给我点生日的仪式感~
起因:我需要一个生日惊喜!
如果大家了解我的话,我是一个喜欢用技术制作某些惊喜的程序员。
「8 月 7 日」是我的阳历生日,没有女朋友的我也是想要生日惊喜的呀 😭,也是想要一点仪式感的呀,于是我便有了用前端技术给自己制作一个生日礼物 🎁 的计划,「正所谓,没有人给你惊喜,你也要学会给自己制造惊喜」。
当然这个生日礼物 🎁 我已经开发完了,于是这篇文章也应运而生。
❝我曾经表示过,我做前端是因为学习前端的过程中,前端曾经给过我很纯粹的快乐与成就感,虽然工作之后前端更加边成了吃饭的家伙 🍚。我相信有很多人和我抱着一样的想法走入前端的职业生涯(至少我有几个同学和我具有一样的出发点),所以我希望我可以用我的努力让更多人发现前端是一个有趣的东西🌹, 我们可以用这个技术去实现一些自己的idea~
❞
前奏:我该如何设计一款生日礼物?
开始的我也是很迷茫,我的生日礼物要有哪些要素呢,于是我就要去审问我自己,我喜欢哪些东西,思考良久。关于我的生日礼物需要哪些要素,我有了答案:
烟花 星空 "生日快乐"这几个字 仰望星空的我
烟花🎆
我想大多数人都会喜欢烟花,大家想象一下无论是动漫还是电影作品,「男孩女孩手拉手走到江畔,或者海边,吹着夏日的风,随着一声炸裂的声响,烟花在天空中绽放,女孩望着烟花入了神,男孩望着女孩出了神,五彩斑斓的光映射在女生的眼眸,像极了他们绚烂的未来,多么浪漫」。哦,我没有女朋友,我秀我自己干嘛呢。。。算了算了,呜呜呜,大家理解这个意境就好。
星空✨
其实我对于星空一直有一个情结,小时候听大人讲,在他们年少的时候,经常可以看到漫天的繁星,而我却没有见过那样的场面,抬头仰望,基本就是寥寥几颗。我也曾想过夜攀高峰,或驻扎山野,抬头望一望漫天星河。
大家想象一下这样的画面,「男孩挽着女孩的手漫步于星空下,仰首是星光灿烂,俯首是挽着的双手与乡间小路,驻足远眺,天空与苍茫大地连接,星河一路延伸直至尽头,美的失了神,男孩轻声细语:“愿你陪我看尽万年雪飘,路经永世繁华,栖身满也星空。”」,哦,我还是没有女朋友,我又秀我自己干嘛呢。。。算了算了,呜呜呜,大家理解这个意境就好。
我的简笔画:一场烟花🎆盛宴,上演于星空🌃之下
烟花,星空,生日快乐,仰望星空的我,四大要素齐了,于是我便开始了我的设计之旅,我先在我的笔记本上画了这样一幅简笔画:
我画的不好,抱歉让各位看客受惊了。但是通过这个画大家可以看到,我这个生日礼物 🎁 要素已经齐全了,天空中有“生日快乐”几个大字,闪烁的星星以及绚烂的烟花,地面上有仰望星空的我~
❝于是我想到了本作品的主题:「一场烟花的盛宴,将在星空下上演」
❞
间奏 🎵
现在的构想和设计已经比较清晰,那么用本章来将我们的设计实现出来吧~ 文章下面的几个章节分别对应上文设计中的:
"生日快乐"这几个字 仰望星空的我 星空 烟花
生日快乐,笔走龙蛇 🎂
我想天空飘来四个字,“生日快乐”,感觉也不是很炫酷,所以就有了一个设想,就是一笔一划的写出生日快乐四个字,就像有人在天空中写下了生日祝福一样,岂不是很炫酷,很有仪式感。
那么问题来了,我如何去实现这样的效果呢,我的第一反应是canvas去绘制这几个字,但是点的坐标很难去找,我又很难去弄出好看的字体,于是在我的纠结之下,我放弃了这个想法。
之后我就去咨询了大帅老猿
之后大帅老师给我推荐了一个这样的库:hanzi-writer, 大家可以去看一看这个文档docs,完美契合了我的需求,于是说干就干。
「html」
<div class="happy-birthday__container">
<div id="sheng"></div>
<div id="ri"></div>
<div id="kuai"></div>
<div id="le"></div>
<div id="ya"></div>
<div id="site"></div>
<div id="xiao"></div>
<div id="han"></div>
<div id="cao"></div>
</div>
「js」
// 文字效果
const BASE_CONFIG = {
width: 100,
height: 100,
padding: 5,
delayBetweenStrokes: 0,
strokeAnimationSpeed: 1.2,
showCharacter: false,
showOutline: false,
}
const WRITER_CONFIG = {
...BASE_CONFIG,
strokeColor: '#e09037'
};
const NAME_CONFIG = {
...BASE_CONFIG,
strokeColor: '#87db92'
};
const getWriterList = () => {
let writerList = [];
writerList.push(HanziWriter.create('sheng', '生', WRITER_CONFIG));
writerList.push(HanziWriter.create('ri', '日', WRITER_CONFIG));
writerList.push(HanziWriter.create('kuai', '快', WRITER_CONFIG));
writerList.push(HanziWriter.create('le', '乐', WRITER_CONFIG));
writerList.push(HanziWriter.create('ya', '吖', WRITER_CONFIG));
writerList.push(HanziWriter.create('xiao', '小', NAME_CONFIG));
writerList.push(HanziWriter.create('han', '寒', NAME_CONFIG));
writerList.push(HanziWriter.create('cao', '草', NAME_CONFIG));
return writerList;
}
const generateAnimateWriter = async (writerList) => {
const writerCount = writerList.length;
for (const writer of writerList) {
await writer.animateCharacter();
}
// 文字全都显示完全后消失掉
document.getElementsByClassName('happy-birthday__container')[0].style.opacity = 0;
}
其实就是官网的用法,我用了七个字,“「生日快乐吖,小寒草」”,生日快乐和小寒草做了分割,样式我就不带着大家一起写了,就是简单的定位。
「效果如下」:
寒草伫立,仰望星空 🔥
下面我们就要去弄出来一个寒草了,大家看我简笔画的姿势并不是我最后想要呈现出来的姿势,因为我这个人是特别喜欢海贼王的,海贼王是有一个经典的姿势的。
大家看过海贼王一定很熟悉这个动作,但是我最后实现出来的不是这样,我把抬起的手臂变成了右手(一定不是因为我太久没看记错了,一定不是!!!),最后我呈现出来的效果是这样的:
注意,看上去简单,但是其实是有很多细节的:
地面的弧度 天空,大地,人物颜色的渐变 人物形状组成 人的影子(没错,在这个寂寥的夜晚,我有影子相伴) 下面我逐一叙述这几个细节的实现方式。
「地面的弧度」:
dom:
<div class="land"></div>
css
.container {
background: linear-gradient(#07112c, #2355d6);
width: 100%;
height: 100vh;
overflow: hidden;
}
.land {
position: relative;
z-index: 20;
height: 60vh;
margin-top: 75vh;
width: 300%;
background: linear-gradient(#072945 0%, #000 30%);
border-radius: 50%;
margin-left: -100vw;
}
很好理解,我给地面三倍的宽度,并通过border-radius 50%
使其变成一个椭圆,并通过定位使其居中,最后给container一个overflow:hidden
「颜色渐变」:
颜色渐变这个很常见了,用的其实就是:background: linear-gradient(#07112c, #2355d6);
背景颜色的线性渐变。
「人物的组成及其阴影」:
遇事不决,先看细节:
先看人物构成,其实就是一个圆(脑袋瓜儿),一个长方形(身体的躯干),三个椭圆(两个胳膊 + 上半身下面的弧度),两个三角形(两只腿)之后通过定位和在一起的的,代码就不在此处展示了。
其实注意的是我的人物其实不是黑色的,然而根据生活经验,我们其实知道影子是黑色的(「话说我开始搞了彩色的影子被朋友指出来,我才想起来,哦对,影子是黑色的!!!」),所以方法也很简单,用两个“人”,一个黑人,一个彩人,黑人用来做倒影,再用彩色的人把黑人盖住,ok,我真的是小机灵鬼~
这里做倒影用的是 css3 里面的 「box-reflect」 属性:
.person-reflect {
-webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.6)));
}
星光闪烁,坠入山河 ✨
看效果图我们可以发现,星星✨的效果其实由两部分组成:
忽大忽小的闪烁效果 下坠的效果(说白了就是弹幕嘛)
「闪烁效果」:
闪烁效果其实就是用的 「keyframes」 动画
@keyframes star-scale {
from {
transform: scale(1, 1);
}
25% {
transform: scale(0.1, 0.1);
}
50% {
transform: scale(1, 1);
}
25% {
transform: scale(2, 2);
}
to {
transform: scale(1, 1);
}
}
.star {
height: 3px;
width: 3px;
background-color: #faf89d;
border-radius: 50%;
position: absolute;
animation: star-scale 2s;
animation-iteration-count: infinite;
}
「下坠效果」:
下坠也是比较常见的了
// 星星效果
function Star(type) {
this.speed = 1;
this.star = document.createElement('div');
this.star.className = type === 'star' ? 'star' : 'moon';
this.star.style.top = '0px';
this.star.style.left = Math.random() * window.innerWidth + 1 + 'px';
document.body.appendChild(this.star);
}
Star.prototype.down = function () {
var that = this;
function move() {
that.star.style.top = that.star.offsetTop + that.speed + 'px';
if (that.star.offsetTop > window.innerHeight) {
clearInterval(timer);
document.body.removeChild(that.star);
}
}
var timer = setInterval(move, 25);
}
let starTimer = setInterval(() => {
new Star('star').down();
}, 300)
哈哈哈,其实大家看代码可以发现我其实原本打算加入月亮的,后来没有什么觉得特别好的效果就取消了~
烟火绚烂,火树银花 🎆
烟花其实就很简单了(ps:看完别人的文章搞出来之后再炫耀式的来一句真简单是不是很找打,哈哈哈哈),这里我是照着大帅老猿的文章学习的,大家感兴趣可以去看看:快过年了,用JS让你的网页放🎆烟花吧
我在动态文字消失后在随机位置绽放烟花:
function fireRandom() {
const x = (canvas.width * 0.7) * Math.random() + canvas.width * 0.2;
const y = (canvas.height * 0.6) * Math.random() + canvas.height * 0.2;
fire(x, y);
}
虽然是随机,但是也不能太随机,要不烟花位置太偏了显示效果不好,所以我就圈定了canvas的部分范围~
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HAPPY BIRTHDAY</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
overflow: hidden;
}
.container {
background: linear-gradient(#07112c, #2355d6);
width: 100%;
height: 100vh;
overflow: hidden;
}
.land {
position: relative;
z-index: 20;
height: 60vh;
margin-top: 75vh;
width: 300%;
background: linear-gradient(#072945 0%, #000 30%);
border-radius: 50%;
margin-left: -100vw;
}
.happy-birthday__container {
position: absolute;
top: 130px;
display: flex;
justify-content: center;
width: 100%;
opacity: .6;
transition: all 2s;
}
.happy-birthday__container>div {
height: 100px;
width: 100px;
margin: 0 10px;
flex: 0 1 auto;
}
.person {
position: absolute;
z-index: 99;
top: calc(75vh - 100px);
left: calc(50vw - 30px);
height: 100px;
width: 60px;
}
.person-reflect {
-webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), to(rgba(0, 0, 0, 0.6)));
}
.head {
position: relative;
height: 40px;
width: 40px;
background-color: #000;
border-radius: 50%;
margin-left: 10px;
z-index: 99;
}
.body-container {
margin-left: 15px;
margin-top: -10px;
width: 30px;
position: relative;
z-index: 20;
}
.body-content {
width: 100%;
height: 35px;
background-color: #000;
}
.body-radius {
width: 100%;
height: 10px;
border-radius: 50%;
background-color: #000;
margin-top: -5px;
}
.legs {
margin-left: 15px;
margin-top: -6px;
height: 30px;
width: 36px;
}
.left {
display: inline-block;
border-right: 7px solid transparent;
border-left: 5px solid transparent;
border-top: 35px solid #000;
}
.right {
display: inline-block;
border-right: 6px solid transparent;
border-left: 7px solid transparent;
border-top: 35px solid #000;
}
.left-hand {
position: absolute;
height: 35px;
width: 10px;
background-color: #000;
border-radius: 50%;
left: 10px;
top: 35px;
z-index: 1;
}
.right-hand {
position: absolute;
width: 9px;
height: 50px;
background-color: #000;
left: 45px;
top: 7px;
border-radius: 50%;
transform: rotate(20deg);
z-index: 1;
}
.person-show>.head {
background: linear-gradient(#072253, rgb(20, 19, 65));
}
.person-show>.body-container>div {
background-color: rgb(20, 19, 65);
}
.person-show>.left-hand,
.right-hand {
background-color: rgb(20, 19, 65);
}
.person-show .legs>div {
border-top-color: #151618;
}
@keyframes star-scale {
from {
transform: scale(1, 1);
}
25% {
transform: scale(0.1, 0.1);
}
50% {
transform: scale(1, 1);
}
25% {
transform: scale(2, 2);
}
to {
transform: scale(1, 1);
}
}
.star {
height: 3px;
width: 3px;
background-color: #faf89d;
border-radius: 50%;
position: absolute;
animation: star-scale 2s;
animation-iteration-count: infinite;
}
#my-canvas {
position: absolute;
width: 100%;
height: 80vh;
}
</style>
</head>
<body>
<div class="container">
<div class="happy-birthday__container">
<div id="sheng"></div>
<div id="ri"></div>
<div id="kuai"></div>
<div id="le"></div>
<div id="ya"></div>
<div id="site"></div>
<div id="xiao"></div>
<div id="han"></div>
<div id="cao"></div>
</div>
<canvas id="my-canvas"></canvas>
<div class="person person-reflect">
<div class="head"></div>
<div class="left-hand"></div>
<div class="right-hand"></div>
<div class="body-container">
<div class="body-content"></div>
<div class="body-radius"></div>
</div>
<div class="legs">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
<div class="person person-show">
<div class="head"></div>
<div class="left-hand"></div>
<div class="right-hand"></div>
<div class="body-container">
<div class="body-content"></div>
<div class="body-radius"></div>
</div>
<div class="legs">
<div class="left"></div>
<div class="right"></div>
</div>
</div>
<div class="land"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/hanzi-writer@3.2/dist/hanzi-writer.min.js"></script>
<script>
// 烟花效果
var canvas = document.getElementById('my-canvas');
var context = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight * 0.8;
}
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
function mouseDownHandler(e) {
var x = e.clientX;
var y = e.clientY;
fire(x, y);
}
var rid;
function fire(x, y) {
createFireworks(x, y);
function tick() {
context.globalCompositeOperation = 'destination-out';
context.fillStyle = 'rgba(0, 0, 0,' + 20 / 100 + ')';
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = 'lighter';
drawFireworks();
rid = requestAnimationFrame(tick);
}
cancelAnimationFrame(rid);
tick();
}
var particles = [];
function createFireworks(sx, sy) {
clearCanvas();
particles = [];
var hue = Math.floor(Math.random() * 51) + 150;
var hueVariance = 30;
var count = 365;
for (var i = 0; i < count; i++) {
var p = {};
var angle = Math.floor(Math.random() * 360);
p.radians = angle * Math.PI / 180;
p.x = sx;
p.y = sy;
p.speed = (Math.random() * 5) + .4;
p.radius = p.speed;
p.size = Math.floor(Math.random() * 3) + 1;
p.hue = Math.floor(Math.random() * ((hue + hueVariance) - (hue - hueVariance))) + (hue - hueVariance);
p.brightness = Math.floor(Math.random() * 31) + 50;
p.alpha = (Math.floor(Math.random() * 61) + 40) / 100;
particles.push(p);
}
}
function drawFireworks() {
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
var vx = Math.cos(p.radians) * p.radius;
var vy = Math.sin(p.radians) * p.radius + 1.2;
p.x += vx;
p.y += vy;
p.radius *= 1 - p.speed / 300;
p.alpha -= 0.005;
context.beginPath();
context.arc(p.x, p.y, p.size, 0, Math.PI * 2, false);
context.closePath();
context.fillStyle = 'hsla('+p.hue+100+', 100%, '+p.brightness+'%, '+p.alpha+')';
context.fill();
}
}
function fireRandom() {
const x = (canvas.width * 0.7) * Math.random() + canvas.width * 0.2;
const y = (canvas.height * 0.6) * Math.random() + canvas.height * 0.2;
fire(x, y);
}
document.addEventListener('mousedown', mouseDownHandler, false);
// 星星效果
function Star(type) {
this.speed = 1;
this.star = document.createElement('div');
this.star.className = type === 'star' ? 'star' : 'moon';
this.star.style.top = '0px';
this.star.style.left = Math.random() * window.innerWidth + 1 + 'px';
document.body.appendChild(this.star);
}
Star.prototype.down = function () {
var that = this;
function move() {
that.star.style.top = that.star.offsetTop + that.speed + 'px';
if (that.star.offsetTop > window.innerHeight) {
clearInterval(timer);
document.body.removeChild(that.star);
}
}
var timer = setInterval(move, 25);
}
let starTimer = setInterval(() => {
new Star('star').down();
}, 300)
// 文字效果
const BASE_CONFIG = {
width: 100,
height: 100,
padding: 5,
delayBetweenStrokes: 0,
strokeAnimationSpeed: 1.2,
showCharacter: false,
showOutline: false,
}
const WRITER_CONFIG = {
...BASE_CONFIG,
strokeColor: '#e09037'
};
const NAME_CONFIG = {
...BASE_CONFIG,
strokeColor: '#87db92'
};
const getWriterList = () => {
let writerList = [];
writerList.push(HanziWriter.create('sheng', '生', WRITER_CONFIG));
writerList.push(HanziWriter.create('ri', '日', WRITER_CONFIG));
writerList.push(HanziWriter.create('kuai', '快', WRITER_CONFIG));
writerList.push(HanziWriter.create('le', '乐', WRITER_CONFIG));
writerList.push(HanziWriter.create('ya', '吖', WRITER_CONFIG));
writerList.push(HanziWriter.create('xiao', '小', NAME_CONFIG));
writerList.push(HanziWriter.create('han', '寒', NAME_CONFIG));
writerList.push(HanziWriter.create('cao', '草', NAME_CONFIG));
return writerList;
}
const generateAnimateWriter = async (writerList) => {
const writerCount = writerList.length;
for (const writer of writerList) {
await writer.animateCharacter();
}
document.getElementsByClassName('happy-birthday__container')[0].style.opacity = 0;
fireRandom();
setInterval(() => {
fireRandom();
} , 2000);
}
const writerList = getWriterList();
generateAnimateWriter(writerList);
</script>
</body>
</html>
尾奏 🎵
冲向未来的极乐迪斯科
在这关键的一天,我当然要去说一说未来的打算,我以我眼中比较浪漫主义的口吻来书写这篇文章,当然在这个环节也是希望把我的未来尽可能说的更加充满浪漫色彩,所以我打算分成几部分来讲。
开源
我们有一个组织:CodingCommunism
最近想做个 「vue3」 的基础ui组件: commi-ui
有很多朋友也参与进来了,感谢大家的陪伴,我们一定可以一起努力,把这个组件库弄好,当然如果大家想围观我们的开发过程,也可以加我的微信 hancao97
,我拉你进群。
当然不仅如此,我们还有很多别的东西:
fe-file-rename automated-interface-testing(这个是我的深坑,我会填的!) ...
生活
曾经说过工作后要去很多很多地方,要去看很多很多风景,今年上半年也算是去了很多地方了,但是最近因为工作和写作的原因搁置了一些原本的想法,但是我这闲不住的心还是在的。
想去西藏看纯净的山纯净的水 想去重庆吃火锅看魔幻的山城 总会出发走上旅程的~
我一直在路上~
难忘今宵~啊~难忘今宵~
又到了今年的生日🎂,我又长大了一岁,前几天已经完成了工作一周年的成就,现在又要完成24周岁的成就,其实有很多感慨,但是我的文笔又难以去很好的表达。
写文半年,工作一年,无论是在我们公司,还是在掘金写作的过程中,都认识了很多很棒的伙伴,获得了很多成长,从曾经磕磕绊绊的萌新到现在也变成了一个可以独立负责一个大模块,完整负责一个定制项目的前端工程师。我想这应该只是开始,一个中二的少年走上沙场,征战四方的开始,我相信我会有更好的未来🔥,大家也是~
今夜我与大家一同伫立于夜空下,望漫天繁星🌃,赏烟花绚烂🎆,耳边回响起每年春节联欢晚会临近午夜时奏响的难忘今宵。
❝难忘今宵,啊,难忘今宵~
❞
我们还会见面的,伙伴们,祝你们的生活变得更加有幸福感~
如果大家喜欢本篇文章,请留下你们的生日祝福🍰吧~
祝大家生活更加精彩~
请你喝杯🍵 记得三连哦~
1.阅读完记得给🌲 酱点个赞哦,有👍 有动力
2.关注公众号前端那些趣事,陪你聊聊前端的趣事
3.文章收录在Github frontendThings 感谢Star✨