Edited at
LIFULLDay 6

ぼくのかんがえたさいきょうの寿司をSassで作る

本記事はLIFULLAdvent Calendar 2018 6日目の記事になります。

こんにちは。新卒3年目のえびといいます。好きな寿司ネタはえんがわです。

最近デザイナーからフロントエンドエンジニアへジョブチェンジしました。

主にLIFULL HOME'S新築一戸建てのコーディング業務をしています。


寿司※

寿司.png

みなさま、寿司はお好きでしょうか。私は大好きです。

特にえんがわが好きですが、サーモンやねぎとろの王道からアボカド・豚カルビなどの変わり種までなんでも食べます。

寿司に貴賤はありません。

どの寿司も誰かのOnly One, Only Sushiです。

とはいえ自分好みの寿司が必ずそこにあるとは限りません。

そこで、今回はSassでフリースタイルな寿司を作ってみたいと思います。

※握り・巻物・ちらしと寿司も千差万別ですが、この記事では寿司=握りと定義します。予めご了承ください


実際に作ったもの

https://codepen.io/ebisawa-next/pen/rQqPEg

以下は解説です。


仕込み


寿司の構成を考える

最初に、寿司とはどんな要素で成り立っているでしょうか。

大まかに考えると以下になるかと思っています。


  • しゃり


    • 白酢、赤酢、ごま混じり...

    • さびの有無



  • ネタ


    • まぐろ、えび、たまごなど



  • 薬味


    • ねぎ、おろし、マヨネーズ



今回はしゃり・ネタ・薬味・さびの有無のパターンを自由に変えられるようにしたいと思います。


寿司.png

スクリーンショット 2018-11-29 8.46.38.png

普通に描きます。今回はSAIで描いたあと、PhotoShopで色を調整しました。

パーツを分離できるようレイヤーをわけ、バランスを見ながら調整していきます。

描き終わったらパーツごとにバラしてpngで出力します。

カテゴリごとにディレクトリを作って格納します。

./images

├ /sharis
│ └ white.png
│ └ red.png
│ └ wasabi.png
├ /netas
│ └ maguro.png
│ └ shake.png
│ └ ebi.png
│ └ tamago.png
├ /yakumis
└ ...


画像取得functionを作る

そのままパスを貼ってもいいですが、

面倒なのでカテゴリと画像名だけ指定すればいい関数をさくっと作っておきます。

// 画像URL取得

/// エスケープのため一旦quoteで囲んだorigin-pathを用意し、
/// その後unquote関数で取り除いて使う
$origin-path: 'https://クソ長いパス';
$path: unquote($origin-path);
@function get-url($category, $name) {
@return url($path + $category + '/' + $name + '.png');
}
///


寿司を作る

準備も終わったので本格的に寿司を握っていきます。


おしながきを作る


Sass

mapで用意します。$sushis直下のkeyは自分の好きな名前で問題ありません。

場合分けしたいしゃり・ネタ・薬味・わさびの有無を書いていきます。

ちなみにネタと薬味は任意なので不要でしたら書かなくても大丈夫です。

$sushis: (

maguro: (
sharis: shari-white,
wasabi: true, //さびあり
netas: maguro,
),
shake: (
sharis: shari-white,
wasabi: true,
netas: shake,
yakumis: mayo,
),
ebi: (
sharis: shari-red,
wasabi: true,
netas: ebi,
),
tamago: (
sharis: shari-white,
wasabi: false, //さびぬき
netas: tamago,
),
hage: (
sharis: shari-white,
wasabi: true,
);


HTML

クラスはmapで指定したkey名にしましょう。

<ul class="sushis">

<li><p class="maguro"></p></li>
<li><p class="shake"></p></li>
<li><p class="ebi"></p></li>
<li><p class="tamago"></p></li>
<li><p class="hage"></p></li>
</ul>


寿司の共通設定を書く

以下のように擬似要素を使ってしゃり(さび)・ネタ・薬味を重ねて表示させます。

p { // しゃり

&::before {} // ねた
&::after {} // 薬味
}

原理としては画像の差し替えだけ別クラスで指定すればよいので、まず共通設定を書きましょう。


Sass

.sushis {

display: flex;
li {
p {
// しゃり
position: relative;
width: 300px;
height: 177px;
background-size: contain;
// ねた・薬味はbefore, afterで乗せます
// ねた・薬味は存在する時だけ出すようにしたいので、contentは別で指定します
&::before, &::after {
display: block;
width: 100%;
height: 100%;
background-size: contain;
position: absolute;
top: 0;
left: 0;
}
&::before {
z-index: 1;
}
&::after {
z-index: 2;
}
}
}
}

あとは最初に作ったお品書きmap@eachで回し、画像を出していきましょう。

@each $key, $value in $sushis {

// map-getでそれぞれのvalueを取得する
$shari: map-get(map-get($sushis, $key), sharis);
$neta: map-get(map-get($sushis, $key), netas);
$yakumi: map-get(map-get($sushis, $key), yakumis);
$wasabi: map-get(map-get($sushis, $key), wasabi);

// $sushisのkeyぶんクラスをつくる
p.#{$key} {
@if $wasabi == false {
background-image: get-url(sharis, $shari);
} @else {
$wasabiUrl: get-url(sharis, wasabi);
background-image: get-url(sharis, wasabi), get-url(sharis, $shari);;
}
@if $neta != null {
&::before {
background-image: get-url(netas, $neta);
}
}
@if $yakumi != null {
&::after {
content: "";
background-image: get-url(yakumis, $yakumi);
}
}
}
}

無事に寿司を表示できました。

スクリーンショット 2018-12-04 22.01.58.png


寿司を並べる

せっかく寿司を作ったので綺麗に並べましょう。


寿司下駄

手軽なところで寿司下駄に乗せます。

力尽きたので寿司下駄はいらすとやから拝借しました。


image.png

https://www.irasutoya.com/2013/04/blog-post_4583.html



HTML

まず寿司ネタを並べます。

今回は5個*2列で並べるので、ul.sushisを二つ用意しました。

後に記載したものが手前にきます。

寿司の記載が終わったら最後に.sushigetaでwrapします。

<div class="sushigeta">

<ul class="sushis">
<li><p class="maguro"></p></li>
<li><p class="shake"></p></li>
<li><p class="ebi"></p></li>
<li><p class="hage"></p></li>
<li><p class="tamago"></p></li>
</ul>
<ul class="sushis">
<li><p class="engawa"></p></li>
<li><p class="negitoro"></p></li>
<li><p class="chutoro"></p></li>
<li><p class="menegi"></p></li>
<li><p class="abokado"></p></li>
</ul>
</div>


Sass

.sushigetaに寿司下駄画像を設定し、ul.sushisを少しずらして配置します。

そしてli@forを使い、nth-child(1〜5)まで少しずつ右下に位置をずらし、寿司が斜めに重なるよう配置します。

// 寿司下駄

.sushigeta {
position: relative;
width: 773px;
height: 419px;
background-image: get-url(kaiten, geta);
.sushis {
position: absolute;
top: -20px;
left: 30px;
// 手前側
+ .sushis {
top: 50px;
left: -80px;
}
li {
position: absolute;
@for $i from 1 through 5 {
&:nth-child(#{$i}) {
left: 100 * $i + px;
top: 20 * $i + px;
}
}
           // 寿司の大きさを調整
p {
width: 200px;
height: 120px;
}
}
}
}

寿司下駄ができました。ちょっとしたランチのようです。おしゅしも紛れ込ませましたが可愛いですね。どうもね!

寿司が綺麗に並んでいるとテンションが上がります。

スクリーンショット 2018-12-05 8.02.30.png


回転寿司

寿司下駄もいいですが、思い入れがあるのは回転している方の寿司なので回してあげようと思います。


皿に寿司を二つのせる

いらすとやから皿を拝借しました。


image.png

https://www.irasutoya.com/2013/12/blog-post_8769.html



HTML

li内で寿司を2つ並べます。

<ul class="sushis kaiten">

<li>
<p class="maguro"></p>
<p class="maguro"></p>
</li>
</ul>

ちなみに今回は同じ寿司をのせていますが、お好み握りのように好きなものを一つずつのせても大丈夫です。


Sass

寿司下駄の応用で、liの背景画像に皿を設定し、pはずれて重なるようにしてあげます。

お皿の寿司は重なる面積が広いため、奥行き感を出すために奥側の寿司はfilterをかけて暗くしておきます。

.sushis.kaiten {

li {
position: absolute;
// 皿の設定をする
width: 150px;
height: 120px;
background-image: get-url(kaiten, dish);
background-size: contain;
background-repeat: no-repeat;
background-position: 0 35px;
animation: slide #{$duration}s linear 0s infinite;

// 寿司を並べる
// 奥に1つめのp, 手前に2つめのpの寿司が来るようにする
// 奥側の寿司はbrightnessで暗くして、重なりを表現する
p {
width: 150px;
height: 88px;
margin-left: 0;
&:first-child {
filter: brightness(90%);
}
&:nth-child(2) {
position: absolute;
left: 20px;
top: 10px;
}
}
}
}

お皿にのった寿司ができました。

スクリーンショット 2018-12-05 20.52.07.png


寿司を回すレーンを用意する

こちらもいらすとやから拝借しました。


image.png

https://www.irasutoya.com/2018/09/blog-post_31.html


ただ、このまま回そうとするとおちゃや醤油皿までgo aroundしてしまうので、

レーンの部分だけ切り抜いて別の画像にしておきます。

lane.png


実際に回す

方法はいろいろあると思いますが、今回は弊社のクリエイターズブログに掲載されている方法を使わせていただきます。

詳しくは以下をご覧ください。


Sassで寿司を回す、たった一つの冴えたやりかた

https://www.lifull.blog/entry/2016/01/19/115424


最終的にソースコードは以下のようになりました。


// 寿司を回すための変数を設定
$itemTotal: 10; // 寿司の総数
$speed: 50; // 寿司の速度

$duration: ($itemTotal*100) / $speed;
.sushis.kaiten {
display: block;
position: relative;
overflow: hidden;
background-image: get-url(kaiten, desk);
background-size: contain;
width: 600px;
height: 338px;
// レーンは擬似要素で無限ループさせる
&::before {
content: "";
position: absolute;
display: block;
background-image: get-url(kaiten, lane);
width: 600px;
height: 48px;
top: 70px;
left: 0;
animation: moveLane 4s infinite linear;
}
@keyframes moveLane {
0% { background-position: 0 0; }
100% { background-position: -600px 0; }
}
li {
position: absolute;
width: 150px;
height: 120px;
background-image: get-url(kaiten, dish);
background-size: contain;
background-repeat: no-repeat;
background-position: 0 35px;
animation: slide #{$duration}s linear 0s infinite;
p {
width: 150px;
height: 88px;
margin-left: 0;
&:first-child {
filter: brightness(90%);
}
&:nth-child(2) {
position: absolute;
left: 20px;
top: 10px;
}
}
}
// 寿司の動き
$offset: $itemTotal * 100%;
@keyframes slide {
0% {
transform: translateX(0)
}
50% {
transform: translateX(-$offset);
visibility: hidden;
}
51% {
transform: translateX($offset);
visibility: hidden;
}
52% {
transform: translateX($offset);
visibility: visible;
}
}
// 寿司ごとのずらし
@for $i from 1 through ($itemTotal) {
li:nth-child(#{$i}) {
$delay: #{($i - 1 - $itemTotal) * ($duration / $itemTotal)}s;
animation-delay: $delay;
}
}
}

うまく回転するようになりました。

スクリーンショット 2018-12-05 20.47.42.png

実際の動きはcodepenでご確認ください。

See the Pen 推寿司 by Moeko Ebisawa (@ebisawa-next) on CodePen.



課題


  • 寿司が増えるたびにmapを追記する必要がある


    • おそらくHTMLはデータベースや配列から引っ張ってforでぶん回すだけでよいのですが、Sass側は手動でmapを書く必要があります



  • 座標指定までやれていない


    • 薬味なんかはもっと細かく指定するべきだし出来なくもないのですが、処理が煩雑になるのでやめた

    • 親要素の大きさが変わる恐れがあるので、やるなら相対でx,yを指定すれば良いと思う



  • 海苔がネタに付随している


    • ex.たまご

    • 海苔までパーツを分けるとDOMが大変なことになるのでネタにつけてしまった

    • とはいえ、たまごに海苔はいらないんだよというユーザーもきっといるはず…




まとめ

mapを使うとちょっとした差分も管理しやすくなるのでよいですね。

今回は寿司でやりましたが、色やグリッド・z-index値をmapで管理すると見通しがよくなるので好きです。

みなさまも快適な寿司ライフをお送りください。