「用技术创造浪漫」前端工程师示爱指南❤️

3,033 阅读6分钟

大家好,我是寒草😈,一只草系码猿🐒。间歇性热血🔥,持续性沙雕🌟
如果喜欢我的文章,可以关注➕ 点赞,与我一同成长吧~
微信:hancao97,加我微信免费看前端早早聊 10.23vue 专场直播

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前奏 🎵

前几天在知乎看到了一些代码情书,就像这种:

#倘若你回首看看我
ifyou.turn_round_aguang()
#我永远在你背后
I.behind(you).forever()
#关注着你
see(you)

其实萌生了总结前端示爱方式的想法,可是看了半天,觉得也就普普通通,甚至有很多示爱小程序给我一种 2000 年代的感觉,比如这个:

image.png

于是我就有了一个想法,自己动手做一个,寒草设计并动手实现的前端工程师特制。

间奏 🎵

image.png

本章涵盖以下内容:

  • 从效果看设计
  • 讲述效果的实现方式

下面,我们开始本文的主要内容。

设计 🤔

给主子.2021-10-02 12_41_23.gif

页面分为两部分

  • 左侧的类终端窗口
  • 右侧的效果显示区

类终端窗口

  • 要有彩蛋
  • 要和右侧效果区呼应

效果显示区

  • 倒计时 ⏰
  • 心 ❤️
  • 奇特的弹幕 📖
  • 月亮 🌛
  • 雨落在水面的涟漪 🌧️
  • 彩色的雨 🌈

开发 😪

将事件串联起来 —— 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 的 心❤️

文案彩蛋

image.png

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

🌈🌈🌈🌈🌈🌈

寒草的示爱宣言 📖

image.png

本章的图片来自电影《侧耳倾听》

因为你,我愿意成为一个更好的人,不想成为你的包袱,因此发奋努力,只是为了想要证明我足以与你相配。

其实小的时候就会被无数作品塑造爱情观,无论是《数码宝贝3》中懵懂的喜欢,启人为救出树莉与基尔兽进化为真红莲形态的红莲骑士兽,还是《食梦者》中各自约定「完成梦想就结婚」后各自的努力拼搏(现实不要这样,否则...我国结婚率又要...),再到最近看《侧耳倾听》中上面的那句话,在我眼里都十分美好而纯粹。

我曾陷入迷茫困惑,也曾陷入焦虑不安,不停的向前,却不知路在何方,被琐事与噩梦缠身又无法自拔。

而现在,距离 2021 年结束还有 84 天,距离新的五年,新的十年规划开启还有 84 天,可以斩旧日枷锁,屠梦魇恶龙,踏七彩祥云,持定海神针...啧,咋感觉越说越不对劲...

咳,重来

而现在,距离 2021 年结束还有 84 天,距离新的五年,新的十年规划开启还有 84 天,可以收拾行囊,整理自己,整装待发,以期未来~

image.png

我想我是否可以改写一下上面的话:

因为你,我渴望成为更好的人,想予你炙热的心,想予你艳丽的羽,想呈现更朝气的自己。因此不仅想发奋努力,又想驻足停留,只是为了想证明我是唯一正确的选择,只为传达我想做你想拥抱的人。

尾奏 🎵

image.png

爱情很珍贵,寒草要努力🌿
深知自己不够优秀
却想要《食梦者》中那般纯粹的梦想与爱情:完成梦想就结婚
以及《侧耳倾听》中:因为你,我愿意成为更好的人。

以及知乎中看到的这句话:

我想,最好的感情是两个人都用力的活,一起体验人生的种种趣味,也能包容与鼓励对方。当对方为你打开新的世界,你就没有因为喜欢一个人而拒绝了整个世界。

-正文完,Hancao design,to be continued-

写在最后

如果神明肯听一听我的愿望,
那么我想你晚安,愿你的梦里总有星星。
这山长水远的路,我想陪你走下去

啊,呼,寒草加油,辛向阳光,怦然辛动。

加我微信:hancao97,邀你进群,一起学习交流前端,成为更优秀的工程师~ 也可以免费看前端早早聊 10.23 日 vue 专场直播,有尤大的分享哦~