【暑假记忆】消暑神器,我用CSS复刻了一个游戏机

2,890 阅读7分钟

“我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛

夏日回忆

我小时候,家里的庭院有一棵枝繁叶茂的树,那时候家里没有空调,夏天会在树荫下铺个席子,或是午睡,或是看书。后来,家里给我买了一台掌上游戏机,那个时候还挺奢侈的。于是,暑假大部分时间都是在俄罗斯方块的旋转、移动中度过的。

我用CSS复刻了一个游戏机

记忆中的游戏机已经模糊了,于是我搜索了一下游戏机的图片。尤其卡通图片,圆乎乎的真可爱。游戏机的主体和屏幕是矩形的,按键是圆形或者圆角矩形。有趣又比较好实现。

所以我便用CSS复刻了一个游戏机。

码上掘金

“码上掘金”可以在线进行代码效果的即时预览、演示,非常方便。下面是游戏机的预览。 code.juejin.cn/pen/7101709…

UI展示

机体

  • 游戏机的主体是一个矩形,带有边框和弧度;
  • 为了呈现立体感,通过box-shadow游戏机主体设置了向外的阴影;

css

.player {
  width: 280px;
  height: 460px;
  background: #ffc600;
  border: 6px solid #5a1f12;
  border-radius: 15px 15px 45px 15px;
  position: relative;
  box-shadow: 30px 20px 10px 10px #9e988b;
}

html

<div class="player"></div>

听筒

掌上游戏机在玩游戏的时候,会有音效,所以我设计了一个听筒。

  • 听筒是一个平面圆角矩形,通过设置border-radius的值可以实现两边的圆角;

css

.head {
  width: 100%;
  height: 26px;
  position: absolute;
  top: 0;
  left: 0;
  border-bottom: 5px solid #5a1f12;
}
.head::before {
  content: '';
  width: 6px;
  height: 26px;
  background: #5a1f12;
  position: absolute;
  top: 0;
  left: 20px;
}
.head::after {
  content: '';
  width: 6px;
  height: 26px;
  background: #5a1f12;
  position: absolute;
  top: 0;
  right: 20px;
}
.receiver {
  width: 50px;
  height: 8px;
  background: #eba260;
  border: 4px solid #5a1f12;
  border-radius: 8px;
  position: absolute;
  top: 5px;
  left: 35px;
}

html

<div class="head">
  <div class="receiver"></div>
</div>

屏幕

屏幕除了边框之外,显示部分由两个内容构成,开机前和开机后,两个不共存。

开机前

  • 开机前展示绿色的屏幕和反光效果,我小时的游戏机屏幕是浅绿色的,这个我还有点印象;
  • 反光效果,主要是为了增加趣味性和表现屏幕的光亮感。我将反光效果设计为一个白条从屏幕上方移动到下方,这个效果通过animation实现;

css

.screen {
  width: 220px;
  height: 170px;
  background: #9c6766;
  border: 5px solid #5a1f12;
  border-radius: 10px 10px 30px 10px;
  position: absolute;
  top: 40px;
  left: 50%;
  margin-left: -115px;
}
.monitor {
  width: 180px;
  height: 120px;
  background: #d5eabd;
  border: 5px solid #5a1f12;
  margin: 20px auto;
  position: relative;
  overflow: hidden;
}
.monitor .light {
  width: 20px;
  height: 220px;
  background: #e2f0d8;
  position: absolute;
  top: -33px;
  left: -23px;
  transform: rotate(44deg);
  animation: monitorlight 3s infinite;
}
@keyframes monitorlight {
  50% {
    left: 80%;
  }
  51% {
    left: 80%;
    opacity: 1;
  }
  61% {
    left: 80%;
    opacity: 0.3;
  }
  71% {
    left: 80%;
    opacity: 1;
  }
  81% {
    left: 80%;
    opacity: 0.3;
  }
  91% {
    left: 80%;
    opacity: 1;
  }
  100% {
    left: 80%;
    opacity: 1;
  }
}

html

<div class="screen">
  <div class="monitor" id="monitor">
    <div class="light"></div>
  </div>
</div>

开机后

  • 开机后展示俄罗斯方块的界面,包括左侧的游戏区域和右侧的游戏记录两个部分;
  • 游戏区域目前仅实现展示游戏名称的功能;

css

.screen .game {
  width: 190px;
  height: 150px;
  background: #d5eabd;
  border-radius: 10px 10px 30px 10px;
  margin: 10px auto;
  display: none;
}
.game .left {
  float: left;
  width: 52%;
  height: 128px;
  border: 1px solid #333;
  margin: 10px 0 0 10px;
  position: relative;
}
.game .game-name {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  color: #000;
  line-height: 1;
  background: rgba(158, 174, 134, 0.8);
  justify-content: center;
  display: none;
}
.game .game-name .name-box {
  animation: 1.5s linear gamename forwards;
  font-size: 15px;
  padding: 54px 0 0 10px;
}
@keyframes gamename {
  30% {
    opacity: 0.3;
  }
  60% {
    opacity: 1;
  }
  90% {
    opacity: 0.3;
  }
  91% {
    opacity: 1;
  }
  100% {
    opacity: 1;
  }
}
.game .left-content {
  position: absolute;
  top: 10px;
  left: 8px;
}
.game .left-box .integral-block.b {
  border: 2px solid #333;
}
.game .left-box .integral-block.b::after {
  background: #333;
}
.game .right {
  float: right;
  width: 30%;
  font-size: 12px;
  font-weight: 300;
  text-align: left;
  line-height: 1.5;
  padding: 10px;
  color: #000;
}
.game .integral-num {
  font-size: 13px;
  font-weight: 500;
  text-align: right;
}
.game .integral-graph {
  position: relative;
}
.game .integral-box {
  position: relative;
  height: 14px;
  width: 40px;
}
.game .integral-box .integral-block {
  border: 2px solid #333;
}
.game .integral-box .integral-block::after {
  background: #333;
}
.game .integral-block {
  display: block;
  width: 6px;
  height: 6px;
  padding: 1px;
  border: 2px solid #9eae86;
  margin: 0 2px 2px 0;
  float: left;
}
.game .integral-block::after {
  content: '';
  display: block;
  width: 6px;
  height: 6px;
  background: #9eae86;
  overflow: hidden;
}
.game .integral-box1 {
  top: 4px;
  left: 13px;
}
.game .integral-box2 {
  top: 7px;
  left: 27px;
}

html

<div class='screen'>
  <div class='game' id='game'>
    <div class='left'>
      <div class='left-content'>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='left-box'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
      </div>
      <div class='game-name' id='gameName'>
        <div class='name-box'>游戏开始</div>
      </div>
    </div>
    <div class='right'>
      <div class='integral-title'>最高分</div>
      <div class='integral-num'>10</div>
      <div class='integral-title'>起始行</div>
      <div class='integral-num'>0</div>
      <div class='integral-title'>下一个</div>
      <div class='integral-graph'>
        <div class='integral-box integral-box1'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
        <div class='integral-box integral-box2'>
          <span class='integral-block'></span>
          <span class='integral-block'></span>
        </div>
      </div>
    </div>
  </div>
</div>;

分割线

  • 分割线为了更好区分屏幕和按键两个区域;
  • 包括左右两个横向和中间六个点;

css

.dividing {
  width: 100%;
  height: 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: absolute;
  top: 220px;
  left: 0;
}
.dleft {
  width: 50px;
  height: 5px;
  background: #5a1f12;
  margin-left: 30px;
  border-radius: 5px;
}
.dcenter {
  display: flex;
}
.dspot {
  width: 3px;
  height: 5px;
  background: #5a1f12;
  margin-right: 3px;
  border-radius: 3px;
}
.dright {
  width: 80px;
  height: 5px;
  background: #5a1f12;
  margin-right: 30px;
  border-radius: 5px;
}

html

<div class='dividing'>
  <div class='dleft'></div>
  <div class='dcenter'>
    <div class='dspot'></div>
    <div class='dspot'></div>
    <div class='dspot'></div>
    <div class='dspot'></div>
    <div class='dspot'></div>
    <div class='dspot'></div>
  </div>
  <div class='dright'></div>
</div>;

按键

  • 上下左右操作按键,由圆和十字图形组成,十字图形通过横向和竖向的矩形组成,为了增加立体感,十字的矩形添加了外部的阴影;
  • 开始按键,右侧倾斜的圆角矩形上侧的红色圆点为开始按键,点击之后,屏幕点亮并展示内容,同时开始按键不可重复点击,停止按键可以点击;
  • 停止按键右侧倾斜的圆角矩形下侧的红色圆点为开始按键,点击之后,屏幕关闭展示绿色屏幕,同时停止按键不可重复点击,开始按键可以点击;
  • 声音按键,底部两个圆角矩形为声音按键;

css

.push-direction {
  width: 80px;
  height: 80px;
  background: #ee9731;
  border: 4px solid #5a1f12;
  border-radius: 50%;
  position: absolute;
  top: 260px;
  left: 15px;
}
.push-direction::before {
  content: '';
  width: 54px;
  height: 20px;
  background: #7b4a45;
  position: absolute;
  top: 30px;
  left: 13px;
  box-shadow: 0 -3px 0 0 #fff;
  border-radius: 5px;
}
.push-direction::after {
  content: '';
  width: 20px;
  height: 54px;
  background: #7b4a45;
  position: absolute;
  top: 13px;
  right: 30px;
  box-shadow: 0 -3px 0 0 #fff;
  border-radius: 5px;
}
.push-control {
  width: 36px;
  height: 80px;
  background: #eb9430;
  border: 4px solid #5a1f12;
  border-radius: 40px;
  position: absolute;
  top: 260px;
  right: 35px;
  transform: rotate(55deg);
}
.push-control .begin,
.push-control .stop {
  width: 26px;
  height: 26px;
  line-height: 26px;
  text-align: center;
  color: #fff;
  background: #ff4822;
  position: absolute;
  left: 6px;
  box-shadow: inset 0 3px 0 0 #fff;
  border-radius: 50%;
  font-size: 13px;
  cursor: pointer;
}
.push-control .begin {
  top: 10px;
}
.push-control .stop {
  top: 45px;
}
.push-sound {
  width: 16px;
  height: 50px;
  background: #864949;
  position: absolute;
  top: 350px;
  border: 2px solid #5a1f12;
  border-radius: 16px;
  box-shadow: inset 3px 0 0 0 #fff;
  transform: rotate(60deg);
}
.push-sound::before {
  content: '';
  width: 24px;
  height: 4px;
  background: #5a1f12;
  position: absolute;
  bottom: 21px;
  left: 12px;
  border-radius: 4px;
  transform: rotate(-90deg);
}
.push-sound-top {
  left: 80px;
}
.push-sound-bottom {
  left: 140px;
}

html

<div class="push-direction"></div>
<div class="push-control">
  <div class="begin" id="pushBegin">B</div>
  <div class="stop" id="pushStop">S</div>
</div>
<div class="push-sound push-sound-top"></div>
<div class="push-sound push-sound-bottom"></div>

js

var leftBox = document.getElementsByClassName('left-box');
var pushBegin = document.getElementById('pushBegin');
var pushStop = document.getElementById('pushStop');
var monitor = document.getElementById('monitor');
var game = document.getElementById('game');
var gameName = document.getElementById('gameName');

// 屏幕展示方法
function show() {
  monitor.style.display = 'none';
  game.style.display = 'block';
  // 设置开始按钮不可以点击
  pushBegin.disabled = false;
  // 设置关闭按钮可以点击
  pushStop.disabled = true;
  setTimeout(function () {
    for (var i = 0; i < leftBox.length; i++) {
      var block = leftBox[i].getElementsByClassName('integral-block');
      for (var j = 0; j < block.length; j++) {
        block[j].classList.add('b');
      }
    }
  }, 800);
  setTimeout(function () {
    gameName.style.display = 'block';
  }, 1500);
}

// 屏幕停止方法
function stop() {
  monitor.style.display = 'block';
  game.style.display = 'none';
  // 设置开始按钮可以点击
  pushBegin.disabled = true;
  // 设置关闭按钮不可以点击
  pushStop.disabled = false;
  for (var i = 0; i < leftBox.length; i++) {
    var block = leftBox[i].getElementsByClassName('integral-block');
    for (var j = 0; j < block.length; j++) {
      block[j].classList.remove('b');
    }
  }
}
pushBegin.onclick = show;
pushStop.onclick = stop;

总结

这次用CSS实现儿时的游戏机,带给我很多启发,一大批卡通形象汇聚到了我的大脑中,等我有时间慢慢实现。

创作的过程真的很有意思,代码成了我的画笔,作为一个不擅长绘画但是喜欢卡通的人,代码帮助我实现了想实现的画作。(^▽^)

不过,还有两个功能待实现。第一个是按行点亮方块的功能,另一个是俄罗斯方块游戏的开发。前面这个最近在探索中。后面的功能,算法是我的短板,也希望未来能补足。