前奏 🎵
前几天在知乎看到了一些代码情书,就像这种:
#倘若你回首看看我 ifyou.turn_round_aguang() #我永远在你背后 I.behind(you).forever() #关注着你 see(you)
其实萌生了总结前端示爱方式的想法,可是看了半天,觉得也就普普通通,甚至有很多示爱小程序给我一种 2000 年代的感觉,比如这个:
于是我就有了一个想法,自己动手做一个,寒草设计并动手实现的前端工程师特制。
间奏 🎵
本章涵盖以下内容:
- 从效果看设计
- 讲述效果的实现方式
下面,我们开始本文的主要内容。
设计 🤔
页面分为两部分:
- 左侧的类终端窗口
- 右侧的效果显示区
类终端窗口:
- 要有彩蛋
- 要和右侧效果区呼应
效果显示区:
- 倒计时 ⏰
- 心 ❤️
- 奇特的弹幕 📖
- 月亮 🌛
- 雨落在水面的涟漪 🌧️
- 彩色的雨 🌈
开发 😪
将事件串联起来 —— Deferred
首先,我需要把事件串联起来,而这个动画中的事件比较复杂,需要把这些事件串联起来,总不能用 setTimeout 回调函数进行嵌套,那样会十分恶心并且不利于管理,于是我使用了 Deferred:
function Deferred() { if (typeof (Promise) != 'undefined' && Promise.defer) { return Promise.defer(); } else if (this && this instanceof Deferred) { this.resolve = null; this.reject = null; const _this = this; this.promise = new Promise((resolve, reject) => { _this.resolve = resolve; _this.reject = reject; }); Object.freeze(this); } else { throw new Error(); } }
说白了就是把 promise 中的 resolve 和 reject 拿出来,用了之后我这套事件顺序就会比较连贯:
stepWord('致 9.15', 'rgb(228, 231, 12)') .then(() => stepWord('开始倒计时', '#c1e8fa')) .then(() => stepWord('start the countdown', '#db61ac')) .then(() => stepSpace()) .then(() => stepWord('🌟🌟🌟🌟🌟🌟')) .then(() => stepSpace()) .then(() => stepTime()) .then(() => stepWord('心跳加速,藏獒开始进化', '#c1e8fa')) .then(() => stepWord('The heart rate quickens and the Tibetan mastiff begins to evolve.', '#db61ac')) .then(() => stepSpace()) .then(() => stepWord('🐶🐶🐶🐶🐶🐶')) .then(() => stepSpace()) .then(() => stepDown()) .then(() => stepWord('致 9.21', 'rgb(228, 231, 12)')) .then(() => stepWord('但愿人长久,千里共婵娟', '#c1e8fa')) .then(() => stepWord('you are my fairy forever', 'red')) .then(() => stepSpace()) .then(() => stepWord('🌛🌛🌛🌛🌛🌛')) .then(() => stepSpace()) .then(() => stepMoon()) .then(() => stepWord('大崽崽', '#c1e8fa')) .then(() => stepWord('big zaizai', '#db61ac')) .then(() => stepSpace()) .then(() => stepWord('☀️☀️☀️☀️☀️☀️')) .then(() => stepSpace()) .then(() => stepRemove()) .then(() => stepWord('但盼风雨来,能留你在此', '#c1e8fa')) .then(() => stepWord("Don't leave me!", '#db61ac')) .then(() => stepSpace()) .then(() => stepWord('🌧️🌧️🌧️🌧️🌧️🌧️')) .then(() => stepSpace()) .then(() => stepRain()) .then(() => stepWord('留你在此,赏彩色的雨', '#c1e8fa')) .then(() => stepWord("I'll create more surprises for you", 'red')) .then(() => stepSpace()) .then(() => stepWord('🌈🌈🌈🌈🌈🌈')) .then(() => stepColorFulRain()) .then(() => stepHanzi())
从前到后一口气写下来,为后面的一大串动画衔接做了铺垫。
终端文案统一处理方法 —— stepWord & stepSpace
stepWord 方法可以配置终端的文案和字体颜色
function stepWord(str, color) { const step = str; const deferred = new Deferred(); const len = step.length; let site = 0; content.appendChild(document.createElement('div')); const child = content.children[content.children.length - 1]; if (color) { child.style.color = color; } const timer = setInterval(() => { child.innerHTML = step.slice(0, site + 1); if (site == len) { clearInterval(timer); deferred.resolve(); } else { site++; } }, 100) return deferred.promise; }
stepSpace 方法可以加入空白行
function stepSpace() { content.appendChild(document.createElement('br')); return Promise.resolve(); }
倒计时 —— stepTime
很简单的替换 dom 元素的内容,并让文字颜色逐渐加深。
function stepTime() { const deferred = new Deferred(); const time = document.getElementsByClassName('time')[0]; time.style.opacity = 1; const number = time.children[3]; const list = ['二', '三', '四', '五']; let site = 0; const timer = setInterval(() => { if (site == list.length) { document.getElementsByClassName('heart')[0].style.opacity = 1; clearInterval(timer); time.style.opacity = 0; deferred.resolve(); } else { number.innerHTML = list[site]; number.style.color = `rgba(233, 62, 59, ${(site + 2) * 0.2})`; site++; } }, 1000) return deferred.promise; }
彩色的雨 —— stepColorFulRain
这里其实这个涟漪的扩散就是简单的帧动画,之后通过 js 在随机位置生成 dom 元素。
css
@keyframes rain { from { transform: scale(1, 1); opacity: 1; } 50% { opacity: 1; } to { transform: scale(100, 100); opacity: 0; } } .rain { height: 3px; width: 3px; background: radial-gradient(rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.3)); border-radius: 50%; transform-origin: 50% 50%; opacity: 0; animation: rain 5s linear; position: absolute; z-index: 999; }
js
function stepColorFulRain() { const deferred = new Deferred(); view.style.background = '#0E2744'; view.style.overflow = 'auto'; function Rain() { this.rain = document.createElement('div'); this.rain.className = 'rain'; this.rain.style.top = Math.random() * window.innerHeight * 0.9 + 0.1 * window.innerHeight + 'px'; this.rain.style.left = Math.random() * window.innerWidth * 0.9 + 0.1 * window.innerWidth + 'px'; this.rain.style.background = `radial-gradient(rgba(255, 255, 255, 0.1), rgba(${Math.random() * 155 + 100}, ${Math.random() * 155 + 100}, ${Math.random() * 155 + 100}, 0.5))`; this.rain.style.animation = 'rain 6s linear' document.body.appendChild(this.rain); const timer2 = setTimeout(() => { clearTimeout(timer2); document.body.removeChild(this.rain); }, 6000) } let starTimer = setInterval(() => { new Rain(); }, 300) setTimeout(() => { deferred.resolve(); }, 2000) return deferred.promise; }
其他的代码
其他的代码都是很简单的 css。
其中这个心形的背景用到了 codepen 的 心❤️
文案彩蛋
致 9.15 开始倒计时 start the countdown 🌟🌟🌟🌟🌟🌟 心跳加速,藏獒开始进化 The heart rate quickens and the Tibetan mastiff begins to evolve. 🐶🐶🐶🐶🐶🐶 致 9.21 但愿人长久,千里共婵娟 you are my fairy forever 🌛🌛🌛🌛🌛🌛 大崽崽 big zaizai ☀️☀️☀️☀️☀️☀️ 但盼风雨来,能留你在此 Do not leave me! 🌧️🌧️🌧️🌧️🌧️🌧️ 留你在此,赏彩色的雨 I will create more surprises for you 🌈🌈🌈🌈🌈🌈
寒草的示爱宣言 📖
本章的图片来自电影《侧耳倾听》
因为你,我愿意成为一个更好的人,不想成为你的包袱,因此发奋努力,只是为了想要证明我足以与你相配。
其实小的时候就会被无数作品塑造爱情观,无论是《数码宝贝3》中懵懂的喜欢,启人为救出树莉与基尔兽进化为真红莲形态的红莲骑士兽,还是《食梦者》中各自约定「完成梦想就结婚」后各自的努力拼搏(现实不要这样,否则...我国结婚率又要...),再到最近看《侧耳倾听》中上面的那句话,在我眼里都十分美好而纯粹。
我曾陷入迷茫困惑,也曾陷入焦虑不安,不停的向前,却不知路在何方,被琐事与噩梦缠身又无法自拔。
我想我是否可以改写一下上面的话:
因为你,我渴望成为更好的人,想予你炙热的心,想予你艳丽的羽,想呈现更朝气的自己。因此不仅想发奋努力,又想驻足停留,只是为了想证明我是唯一正确的选择,只为传达我想做你想拥抱的人。
尾奏 🎵
爱情很珍贵,寒草要努力🌿
深知自己不够优秀
却想要《食梦者》中那般纯粹的梦想与爱情:完成梦想就结婚
以及《侧耳倾听》中:因为你,我愿意成为更好的人。
以及知乎中看到的这句话:
我想,最好的感情是两个人都用力的活,一起体验人生的种种趣味,也能包容与鼓励对方。当对方为你打开新的世界,你就没有因为喜欢一个人而拒绝了整个世界。
-正文完,Hancao design,to be continued-