Alpine.jsでドラッグ操作による並び替えを実現する方法(Sort Plugin)

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はAlpine.jsの公式プラグインを使用して、ドラッグ操作による並び替えを実現する方法をご紹介します。

使用するプラグインについて

Alpine.jsでドラッグ操作による並び替えを実現するためには、公式に提供されているSort Pluginを使用します。

Alpine.js本体と同様にscriptタグを使用してCDNから読み込むことが可能です。(npmも使用できます

その際、Alpine.js本体を読み込むscriptタグの前に記述する必要がありますのでご注意ください。

<!-- Alpine.js本体を読み込むscriptより前に記述する -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/sort@3.14.1/dist/cdn.min.js"></script>

実装方法

まずは基本形をお示しします。

ぜひCodePenに触れてみてください。

See the Pen Untitled by AyumuSaito (@ayumusaito-excite) on CodePen.

<div x-data>
  <ul x-sort>
    <li x-sort:item>アイテム1</li>
    <li x-sort:item>アイテム2</li>
    <li x-sort:item>アイテム3</li>
  </ul> 
</div>

並び替えを適用したいアイテム群のラッパーにx-sortディレクティブを、個々のアイテムはx-sort:itemディレクティブを付与すると、ドラッグ操作による並び替えが可能になります。

並び替えの情報を取得する方法

x-sort:itemにIDを振ると、x-sort内でアイテムIDは$item、並び順は$position(始まりは0番目)で取得できます。

※CodePen内でアイテムを動かすと、ブラウザのアラートダイアログで動かしたアイテムのIDと並び順が確認できます。

See the Pen Alpine.jsのSort Pluginで並び替えを実現(ハンドラー) by AyumuSaito (@ayumusaito-excite) on CodePen.

<div x-data>
  <ul x-sort="alert('アイテム' + $item + 'を' + $position + '番目に移動しました')">
    <li x-sort:item="1">アイテム1</li>
    <li x-sort:item="2">アイテム2</li>
    <li x-sort:item="3">アイテム3</li>
  </ul> 
</div>

以下のようにすると、x-data内でも並び替えられたアイテムのIDと並び順を扱うことができます。

<div x-data="{ handle: (item, position) => {alert('アイテム' + item + 'を' + position + '番目に移動しました')} }">
  <ul x-sort="handle">
    <li x-sort:item="1">アイテム1</li>
    <li x-sort:item="2">アイテム2</li>
    <li x-sort:item="3">アイテム3</li>
  </ul> 
</div>

さいごに

今回はAlpine.jsの公式プラグインを使用して、ドラッグ操作による並び替えを実現する方法をご紹介しました。

公式ドキュメントでは、他にもグルーピングやドラック可能領域の設定の方法などが紹介されていますので、ぜひ目を通してみてください。

Alpine.jsを使用されている方の一助となれば幸いです。ご精読ありがとうございました。

【Flutter】ListViewではshrinkWrap:trueを使うよりもSliversウィジェットを使うべき!?

こんにちは、エキサイト株式会社でアプリエンジニアをしている岡島です。今回はListViewを入れ子にする場合や、他のウィジェットと組み合わせる際に、注意すべきshrinkWrapプロパティの使用とSliversウィジェットについて取り上げたいと思います。

shrinkWrapプロパティとは

shrinkWrapプロパティは、子ウィジェットの高さに合わせて動的にListViewの高さを設定するものです。

デフォルトでは、falseになっていて、ListViewに高さの制限がない場合は、ListViewは最大サイズまで拡大されます。

shrinkWrapのtrueとfalseの違い

shrinkWrap: false(デフォルト)

  1. サイズ
    ListViewは、親ウィジェットから与えられた制約内で可能な限り大きくなります。
  2. パフォーマンス
    画面に表示されている項目のみをレンダリングするため、効率的です。

shrinkWrap: true

  1. サイズ
    ListViewは、子ウィジェットの合計高さに基づいてサイズが決定します。
  2. パフォーマンス
    すべての子ウィジェットを一度に評価するため、大量の項目がある場合はパフォーマンスが低下する可能性があります。

shrinkWrapの問題点

上記のように、shrinkWrap: trueの場合はListView表示時にレンダリングの仕組みが違います。

ListView.builderは通常、画面外のウィジェットを構築しないようになっていますが、 shrinkWrap: trueを設定すると、画面外のウィジェットも含めてすべてのウィジェットが構築されてしまいます。

そのため、リストに多数のウィジェットがある場合や、アニメーションを含む場合、パフォーマンスが著しく低下する可能性があります。

詳しくは↓こちらの動画で詳しく説明されているの ぜひご覧ください。 https://youtu.be/LUqDNnv_dh0?feature=shared

Sliversウィジェットを利用して、パフォーマンスを向上させる

shrinkWrapを使用する代わりに、Sliversウィジェットを活用することで、効率的なリスト表示を実現できます。Sliversウィジェットは、スクロール可能な領域を小さな部分に分割し、必要な部分のみを描画することでパフォーマンスを向上させます。

つまり、Sliverウィジェットを使用することで、現在の表示領域にあるウィジェットのみがレンダリングされるようになり、パフォーマンスの向上に繋がるのです。

↓サンプルコードを書いてみたので、画面描画の違いを確認してみてください。 https://dartpad.dev/embed-dart.html?id=6ac4d9e48d18129402c4ec6749731b8a

ListViewでネストを実装
SliverListでネストを実装

まとめ

今回は、ListViewのshrinkWrapプロパティの真偽値によって変わるレンダリングの仕組みを見てみました。適切に影響を理解することで、より良い実装を考えることができます。この情報が皆様のアプリ開発のお役に立てれば幸いです。ご精読いただき、ありがとうございました。

就業型インターン Booost!!!で得た学び

はじめに

2024年9月から1ヶ月間、エキサイト株式会社のSaaS事業部で就業型インターンシップ「Booost!!」に参加させていただきました。インターンを通して経験したことや学んだことを紹介します。

自己紹介

理学系の大学院の修士1年で、電離圏についての研究を行っています。大学3年生のときにプログラミングに出会い、ソフトウェア開発に興味を持ちました。現在はフロントエンドの開発を主に行っています。Firebaseが好きでよく使っています。今回のインターンシップではチーム開発とエンジニアの働き方を実際に体験したいと思い、参加させていただきました。

インターンに参加するまでの技術スタック(2024/09現在)

私のこれまでの開発経験をまとめたいと思います。

Python: 2年

TypeScript: 半年

  • Reactを用いたフロントエンド開発を行ってきました

業務内容

SaaS事業FanGrowthの開発に携わりました。 FanGrowthでは、以前までフロントエンドをVue.jsを用いて作られていましたが、現在Reactの移行を進めています。

今回のインターンではReactへの移行業務をさせていただきました。 具体的な開発内容としては、共通コンポーネントの作成API連携画面作成等の機能を実装しました。

働き方について

全てフルリモートで業務を行いました。

以下のスケジュールで稼働しました

  • 10:00 ~ 13:30: 開発
  • 13:30 ~ 14:30: 休憩
  • 14:30 ~ 18:30: 開発

(日によっては、ミーティングや面談等もありました)

開発では、JIRAを用いてタスクごとにチケットが発行されており、割り当てられたチケットの開発を行いました。 チケット発行→開発→レビューというフローを繰り返して行いました。 ミーティングでは、エンジニア内の進捗報告会の参加や、事業全体に関わる話し合いにも参加させていただきました。

人事、メンター、上長の方々と面談をさせていただくこともありました。

インターンを通して学んだこと

技術について

これまで個人開発をメインに行ってきたため、他の開発者が書いたコードに触れる機会が少なかったですが、インターンを通して実務のコードに触れる経験をすることが出来ました。他人の書いたコードを理解して新しい機能を実装することが想像以上に難しかったです。

また、機能を実装する際、UIについてもデザイナーの方が作成したデザインを基に実装する必要がありました。個人開発では、どうやってお洒落なUIを作るかという意識でコーディングをしていましたが、実務では「デザイン通りに実装する」ことが求められました。CSSの細部まで理解していなかったため、デザイン通りに実装することは想像以上に大変でした。

開発のルールに柔軟に対応することも難しかったです。

例)開発のルール

特に、印象的だったのが共通コンポーネントの粒度CSSの2点です。

1点目の共通コンポーネントについてですが、これまで個人開発でボタンやフォームなどを共通化する程度で、基本的にその場でUIを記述していました。しかし、FanGrowthではボタンやフォームはもちろん、FlexCard, Gridといったレイアウト要素までコンポーネント化されており、とても新鮮に感じました。

2点目のCSSに関しては、main.cssに色などのスタイルを定義して、そこからインポートしてスタイルを適応していました。個人開発では、その場でスタイルを記述していたため、ページごとのデザインの統一感が課題になっていました。しかし、この方法を取り入れることで、統一感のあるUIを実現できることを学びました。

また、触れたことのない技術(Storybook, Radix, SWR等)が多くあり、それぞれキャッチアップする必要がありました。個人開発では技術ブログや生成AIに頼りがちでしたが、インターンでは公式ドキュメントから情報を得るように心がけ、新しい技術のキャッチアップを行いました。特に、Storybookは実際に画面に描画させる必要がなく、Storybookのサイト上に描画をしてくれるので、共通コンポーネントを作成する際には非常に便利なライブラリだと感じました。個人開発でも是非導入してみたいです。

働き方について

業務ではエンジニア社員の方々と近い内容で日々業務を行いました。 Tandemというチャットツールが導入されており、リモートでもコミュニケーションを常にとれる環境がありました。 リモートではありましたが、チーム全体で作業している雰囲気を味わうことが出来ました。

セルフマネジメント力

業務を通して印象に残ったのは、タスク管理能力が想像以上に求められる点です。開発タスクに加え、ミーティングや面談、コードレビューなど、多岐にわたるタスクがあります。リモートワークという特性上、これらのタスクを自分で管理し、適切に優先順位をつける必要がありました。

また、開発中に分からない点に数時間費やしてしまうことが何度かありましたが、質問することで即座に解決するケースもありました。自分1人で解決できるかどうか判断するためにも、常に理解度を意識することが重要だと感じました。理解度に応じて取るべき行動(公式ドキュメントを読む、ソースコードを読む、質問する等)を適切に選択することが大切です。 質問をする際には、課題と解決したい点を言語化して、質問させていただく方の時間を無駄にしないように心がけました。

インターンシップを通してタスク管理能力質問力はエンジニアとして重要な要素であると実感し、今後も意識していきたいと思いました。

最後に

メンター、同じチーム、人事の方々に支えられながら1ヶ月間を過ごすことが出来ました。 社員のみなさんが優しく接してくださり、最後まで楽しく業務を行えました。 技術的な質問があった際に、相談できる環境があり心強かったです。 インターンを通して学んだことを今後のキャリアに活かしていきたいと思います。 本当にありがとうございました!

エキサイト株式会社の就業型インターンシップに参加して

こんにちは! 2024年9月9日〜9月30日まで約1ヶ月程度、メディア事業部で就業型インターンシップに参加させて頂いた渡邉です。ここでは今回のインターンシップの内容やインターンを通して学んだことなどを書いていこうと思います。インターンを検討している方などの参考になれば幸いです!

自己紹介

私は情報学を専攻している修士1年生で、自然言語処理の研究室に所属しています。研究ではChatGPTなどの生成AIを使用してテキストベースの人狼ゲームをプレイすることが可能なエージェントの作成を行っているほか、作成したエージェントの精度を評価するために他の大学の人が作成した人狼エージェントと対戦を行うための大会の主催などをしています。

インターンについて

インターンの形式

ここでは自分が実際にインターンを行った形式について書こうと思います。あくまでも自分の場合なので参考程度にはなりますが、フルリモートでの勤務なのでとても働きやすく、就職後にリモートで働く場合の感覚を掴みやすかったです。インターンの開始期間については調整が利きやすく、私の場合は9月2日から参加することができましたが、他の用事があったため9月9日からの参加にさせて頂きました。勤務時間も調整が行いやすく、開始時間・終了時間の調整や中抜けなども自分の用事に合わせて行いやすかったです。

  • フルリモート
  • 期間: 9月9日〜9月30日
  • 勤務時間: 10:00〜18:30

インターンの業務内容

インターンの業務として、エキサイトブログで今後リリース予定であるPDFを発行する新機能の追加の業務に携わらせていただきました。

主にバックエンドを担当させて頂いたのですが、PDF作成のための技術調査から始まり、実際に調査した技術が使用できるかエンドポイント立てて確認、DBから値を取得してPDFに適応させることやPDF発行に伴い保存する必要のあるデータを保存するためのテーブルの作成などを行いました。 技術調査からapiの開発、DBのテーブル設計やコードレビューを受けるなど1つの機能開発の一連の流れを経験させて頂けてとても勉強になる1ヶ月間でした!

就業型インターンであるため実際のプロダクトの機能開発をすることができ、多くの人が使用する機能を自分で開発することができたのでとてもやりがいがありました。

インターンで使用した技術

研究やアルバイトでLaravelやJavaGitHubを使用したことはありましたが、それ以外のフレームワークや開発ツールはインターンで初めて使用しました。今まで触れたことのないフレームワークなどに初めは不安でしたが、分からない部分を丁寧に教えていただけたので特に問題はありませんでした。

インターン中のイベント

インターン期間中にオフラインで開催されるイベント「BeerBash」に招待して頂きました! 私は遠方から参加することもあり、有り難いことにイベント当日はオフィスで業務をさせて頂いてからイベントに参加する形になりました。BeerBashではカンファレンス参加レポや各事業部ごとにインターン生からの質問に答える対談があり、とても興味深いお話を聞くことができました。また、フリータイムには様々な方とお話しさせて頂くことができ、とても貴重な体験となりました。 私が参加した回のBeerBashについて記事が書かれているようなので、そちらを参考にして頂くとイメージしやすいかもしれません。

tech.excite.co.jp

学んだこと

クエリの発行は少なくすることを意識する

インターンを初めて間もない頃は処理を簡潔に書くことの意識はしていましたが、書いた後に無駄にクエリを発行していないかなどを確認する意識がなく、コードレビューを頂いて無駄にクエリを発行していたことに気がつきました。クエリの発行は多いほど負荷がかかり実行時間が遅くなってしまい良いことがないため、可能であれば減らした方が良いです。これはクエリの発行に限りませんが無駄な処理がある場合はコードも冗長になってしまうため、今後の読み手のためにも処理をまとめることはできないか考えることは重要であることを改めて学ぶことができました。

順番を意識して書く

先ほどの話と繋がりますが、これもコメントを頂いて意識しようと思った点です。PDFに必要なデータを取得するために既存のクエリにINNER JOINで新たにテーブルの結合をしようとしました。以下はその例ですが、新しくhogehogeテーブルを内部結合するためにFROM句の末尾にINNER JOIN hogehogeを追加したクエリを作成しました。

SELECT
   *
FROM
   hoge
   INNER JOIN fuga
      USING(id)
   LEFT OUTER JOIN piyo
      USING (id)
   INNER JOIN hogehoge
      USING(id)
   WHERE ...

今回私が行ったタスクの場合、クエリの順番としてはこれでも欲しい情報を得ることはできましたが、INNER JOINLEFT OUTER JOINが混ざってしまって読み手にとって理解しずらいクエリになってしまいました。既存のクエリに追加する場合に安易に末尾に追加するのではなく、以下の例のようにINNER JOINLEFT OUTER JOINを分けて書くことで理解しやすい形式にし、レビューなどをしやすいコードを書くことを意識したいと感じました。

SELECT
   *
FROM
   hoge
   INNER JOIN fuga
      USING(id)
   INNER JOIN hogehoge
      USING(id)
   LEFT OUTER JOIN piyo
      USING (id)
   WHERE ...

また、今回のタスクでは問題ありませんでしたが、内部結合や外部結合は順番によって結果が変わるのでしっかりと考えて変更を行うようにしようと思いました。

変数の値を追いやすくする

またしても可読性についてで、レビューを頂き意識しようと感じた点です。PDFに値を埋め込む際に、inputタグから値を取得できたかどうかで代入する値を変更する処理を書く場面があり、その際に私は以下のようなコードを書きました。

$value = [
    "id" => $_POST["id"],
    "name" => $_POST['input']
];

if(!isset(value["name"])){
    $value["name"] = $_POST['another_input'];
}

上記のコードは$value["name"]が色々な箇所で値が変更されていて、$value["name"]の値に注目したい場合にどこで変更されたのか追うためにコードをしっかり見る必要があり大変です。そのため、以下のコードのように三項演算子など使用して値を変更する箇所を1つの場所にまとめ、変数の値を追いやすいコードにしたいと思いました。

$name= isset($_POST['input']) ? $_POST['input'] : $_POST['another_input'];

$value = [
    "id" => $_POST["id"],
    "name" =>$name
];

ここで示した例はPHPでしたが、apiを実装したSpring Boot(Java)ではfinal修飾子が存在するため、値が変更されない場合はfinal修飾子をつけることで可読性を向上させることを自然に意識できるようにしたいと感じました。

DBのテーブルについて

エキサイトブログは長年続いていることもあり運用を行いやすくするための指針がとても興味深かったです。実際の例として、新たに記録したい情報が出てきた場合に安易に既存のテーブルにカラムを追加せずに新しいテーブルを作成する理由がとても勉強になりました。カラムを追加する方針を採用すると既存のデータはそのカラムにNULLなどの値を入れることとなり、無意味なデータを大量に保管する上にデータを追い辛くなることを実際に大量にNULLがあるテーブルを見て実感しました。

まとめ

約1ヶ月間のインターンシップの中で新機能の開発に一から携われるという非常に貴重な体験をすることができ、インターンを始める前よりも技術的にも成長することができたと感じています。開発を通し、チーム開発を行う上で気をつけるべき点などを学ぶことができたので、今後はこれを活かせるように意識して開発をしていきたいです!開発だけでなく、イベントを通してエキサイトの雰囲気を知ることができたので、非常に有意義な体験ができたと感じています。他ではなかなかできない貴重な経験ができるので、インターンに参加するか迷っている人には是非進むことをお勧めします。

最後に

初めて触る技術が多くチーム開発の経験も少ないので至らぬ点も多々あったと思いますが、メンターの方をはじめとする多くの方にサポート頂くことでインターンシップをやり切ることができました。この場を借りて感謝申し上げます。

第9回テクデザBeer Bashを開催しました

こんにちは、エキサイトでエンジニアをしている吉川です。 先日9/13(金)に社内イベントの「テクデザBeer Bash」を開催したので、運営視点でレポートを書いていきます。

テクデザBeer Bashとは

Beer BashとはBeer(ビール)+ Bash(にぎやかなパーティー)を合わせた造語で、真面目な部分を残しつつ、カジュアルな雰囲気で交流を行うイベントです。

年に3、4回社内カフェスペースで開催しており、同じチーム内の人はもちろん、業務ではあまり関わることがない他部署の人たちとも繋がる場になっています。

当日のコンテンツ

今回は以下の2つをメインコンテンツとし、後半はフリートークの時間にしました。

  • 外部カンファレンス参加レポ
  • テクデザ対談

外部カンファレンス参加レポ

PHPerKaigiなどの社外カンファレンスにご参加されたエンジニアから、参加したきっかけや社外コミュニティの雰囲気のついてお話ししていただきました。 後半ではご自身が運営されたコミュニティのお話しもあり、コミュニティ運営の楽しさや難しさを伝えていただきました。

テクデザ対談

プラットフォーム事業チーム、ブロードバンド事業チームの各エンジニアとマネージャーによるパネルディスカッションを行いました。 若手の活躍の様子や、チームごと空気感の違いが現れており、盛り上がるコンテンツになりました。

運営視点の振り返り

今回から採用・広報活動の一環で、インターンなどの学生さんや選考中の方を積極的にご招待するようになりました。 メインコンテンツも社内の雰囲気を感じ取ってもらえるように工夫しており、参加されたインターン生の方々からもご好評をいただきました。

また今まで食事は各座席に配膳していたのですが、試験的にバイキング形式にしたところ、フリートークでの参加者の流動が良くなり多くの交流を産むことができました。 参加後のアンケートでは、8割以上の社員が普段関わりの薄い方やインターン生と交流できたと回答いただき、手応えがあったと感じています。

一方でテクデザ対談の時間配分については反省点です。各チーム15分で3〜5つトークテーマを準備していましたが、実際やると1、2個目で15分を超えてしまい、「もっと聞きたかった。でも全体としては長かった。」という感想が出てしまいました。タイムキープは今後もっと意識していきます。

最後に

最後まで読んでいただき、ありがとうございました。 テクデザBeer Bashは社内イベントではありますが、少しでもイベントの雰囲気が伝わっていたら幸いです。 次回はついに2桁に突入します。3年弱続けてきた企画ですが、だんだんと社内文化として定着してきたところがあるので、引き続き頑張ろうと思います!!

エキサイトではフロントエンドエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。 長期インターンも歓迎していますので、興味があればご連絡ください。 募集職種一覧はこちらになります!(カジュアルからもOK)

www.wantedly.com

ブロードバンド事業部で就業型インターンシップを通して

2024年9月の1ヶ月間エキサイト株式会社が開催した「Booost!!! Excite Internship 2024」で経験したこと、感じたことをこの記事を通して紹介したいと思います。

自己紹介

私は商学部に在籍している大学3年生です。大学内の授業ではプログラミングやコンピューターサイエンスの科目がほぼ無いと言って過言ではありません。そのため、私は「Recursion」という学習サービスを利用し、ソフトウェア開発やコンピューターサイエンスを学んできました。個人での開発物として、複数のバックエンド及びフロントエンドプロジェクトがあります。扱ってきた言語、技術は習熟度の差はありますが、JavaScript、TypeScript、PHP、Go、Python、Reactなどです。卒業後はエンジニアとして働くことを目指しています。

インターンシップについて

参加部署

ブロードバンド事業への参加となりました。ここは「BBエキサイト」「エキサイトモバイル」などを展開している事業部です。

業務内容

「メール本文テスト機能」の実装を担当しました。この機能は開発者向けとなっておりユーザーは使用できないものとなっております。機能面としましては、セレクトタブから確認したいメールのタイトルを選択し、その後対応したメール本文が画面上に表示されます。また「メール送信機能」も実装されており、任意のメールアドレス先まで送信することができます。

イメージ画像

使用技術について

今回の実装では、PHPフレームワークであるLaravelでバックエンド部分を構築し、フロントエンド部分をVue.jsで実装しました。私はPHP及びJavaScriptでの開発経験はありますが、フレームワークを使用しての開発は今回が初めてとなります。

なぜこの機能が必要か?

私がインターンシップ参加前から事業部内で

「メールテスト機能があればいいよね」

との意見があり、今回私が担当することとなりました。

この機能の利用目的として

  • 定期的に複数パターンのメール文を確認する必要がありましたが、この機能を実装することで工数を削減できる
  • メールの文言調整の際に、実際に送られる最終的なメール本文を確認できる
  • プロデューサーの方も確認したい場合がある

があげられます。

開発の流れ

Controller.php、Index.vue、メールテンプレートに対応するconfigファイルを新たに作成しました。そして下記の順序で開発を進めました。

  1. 既存のメールテンプレートごとに、メール本文で使用する変数の設定値をconfigファイルにまとめる
  2. configファイルからメールテンプレートの設定の一覧を取得
  3. 画面にプルダウンで一覧を表示
  4. プルダウンで特定のメールテンプレートが選択されたら、APIでconfigファイルから変数値を取得してbodyに埋め込んだ文字列をtextareaなどに表示
  5. 「送信」ボタンで実際にメールを送信する

開発を通して気づいたこと、身についたこと

  • 開発に取り掛かる前に、開発の全体像を把握しなければならないと感じました。今回はすでに存在しているプロジェクトに途中から参加した形となっているため、これまでどのようなコードの書き方をしているか、ファイル構成はどのようになっているかを確認しつつ作業を進めました。加えて今回はメンターの方が主導で要件書を作成したのですが、今後は自分で作成できるようにしなければならないと感じました。
  • ファイル名、フォルダ名、関数名、変数名などは他の人から見たときでも、ここで何をしているのかを理解しやすいように宣言する必要があると感じました。
  • 開発に行き詰まった時は、デバックを利用し、今どのような値を受け取っているか、型の種類は何か等を意識しつつ開発を進めました。
  • これまで個人開発がメインで自分が書いたコードをレビューしていただく機会がありませんでした。レビューを通して、自分が書いたコード内で必要ではない箇所や変更した方が良い箇所を指摘していただきました。繰り返しになりますがチームで開発を行う際は自分のコードが見られていると意識することが必要であると感じました。

 

まとめ

1ヶ月という短い期間でしたが、実際にエンジニアとして働くことがどのようなものかを経験することができました。また、今回は全日程リモートでの参加となりましたがメンターの方にサポートしていただき開発に取り組むことができました。加えて、今回の開発とは直接関係ない部分であっても、エンジニアとして必要な考えや意識しておいた方がいいことを随時教えていただき、とても有意義な期間でした。

最後に

メンターさんや人事の方にサポートしていただきながら、素晴らしいインターンシップ期間を過ごすことができました。1ヶ月間至らないところが多々ある中で、さまざまな経験をさせていただきありがとうございます。

就業型インターンシップ「Booost!!!」に参加して得た貴重な学び

はじめに

こんにちは!9月初旬から1ヶ月の間、エキサイト株式会社のLife&Wellness事業部で、就業型インターンシップ「Booost!!!」に参加させていただいた須藤です。今回は、インターンシップでの取り組みや学んだことを紹介します。

自己紹介

私は大学院で、異常検知分野の研究を行っています。これまでWebアプリケーションの開発の経験は、趣味の範囲で簡単なものを作成した程度であり、Webアプリの本格的な開発や保守運用の経験はなく、今回のインターンシップが初めての挑戦となりました。このインターンシップに参加した目標は、就業型の実務インターンを通して、チーム開発の経験を積み、キャリアイメージや、エンジニアとして働く解像度を上げることです。その中で技術的な知識も加え、エンジニアとして求められることを学びたいと考えていました。

インターンシップの業務内容

Life&Wellness事業部は、「エキサイト電話占い」という占いサービスを提供しています。私は、「エキサイト電話占い」で使用されているPHPフレームワークBEAR.Saturdayを、最近の主流フレームワークであるLaravelに移植する業務に取り組みました。その中で、アンケートページやそのアンケート回答の感謝を表示するページの移植を行いました。私は、Ruby on Railsの経験が少しあったものの、PHPやLaravelには今回初めて触れました。そのため、最初はロジックを含まない静的なページの表示から始めさせていただき、徐々に段階を踏みながら、より複雑なタスクを割り振っていただきました。また、毎回朝会で、進捗の報告や今日の目標の確認をする時間があり、タスク目標を明確にすることができました。さらに、業務の過程で、丁寧なPRレビューやアドバイスに加え、どのようにサービスが動いているのかというような詳細な補足説明もいただき、全体像を掴みながら、順調に慣れることができました。

学んだこと

このインターンを通して、数多くのことを学ぶことができましたが、その中でも特に印象に残ったことや、実務でのチーム開発を経験したからこそ得られた知見を、コーディング面とチーム開発/コミュニケーション面でまとめようと思います。

コーディング

データ処理の流れを理解する

このインターンで初めて、PHPやLaravelに触れたということもあり、アーキテクチャの理解が大変でした。その中でも、アンケートの回答データをAPIを使用して送信をするまでに、データ処理のプロセスを把握することが一苦労でした。ディレクトリ構成が決められていて、回答データに関するファイルがたくさんあるということもあり、まずはsampleファイルを参照しながら、データがどのように処理されていくのかを、以下のように自然言語で簡単に書き出してみました。

  • ルールに基づいてバリデーションチェックを行う(postForm)
  • チェックがOKだった場合:入力されたデータをUseCaseで処理する
    • [UseCase内]
      • $Answers = new Answers($userId, ...); (Modelのインスタンスを作成)。
      • リポジトリを利用して回答結果を送信 Post ParamsでAPIに受け渡すデータを設定。

書き出してみたことで、データ処理の流れを自分の中で明確にでき、開発がスムーズに進められた気がします。とりあえずコードを書き進めてみるのではなく、方針や全体像を把握することの重要性に気づきました。

テストケースの作成

私の中でかなり躓いたところが、テストケースの作成でした。テストケースを作成するにあたり、モックの理解が非常に重要でした。 例えば以下のコードは、URLのパラメータにidがセットされている場合に、idを保存するuseCaseのhandle処理が呼び出されているかどうかを検証するテストです。

public function test_パラメータにidが指定されているときに、idの保存処理handleが呼び出されていること(): void
    {
        // 1.準備
        $id = 'test_id';
        $this->mockAuthentication(dummyUserId: self::TEST_USER_ID);

        $this->mock(EnqueteIndexGetUseCaseInterface::class, function ($mock) use ($id) {
            //3. 検証
            $mock->shouldReceive('handle')
                ->once()
                ->with($id);
        });

        //2. 実行
        $response = $this->withUnencryptedCookies([ApiConstant::COOKIE_KEY => 'test_value'])->get(
            uri: route(EnqueteIndexGetController::ROUTE_NAME, [EnqueteIndexGetController::GET_PARAMETER__ID => $id]),
        );
    }

ここで、shouldReceive('handle')は、「handle()が呼び出されることを期待する」ということを定義します。同様に、once()は、「handle()が1回呼び出されることを期待する」ということを表しています。本来は、useCaseをモックして、どのような動作が望ましいのかを定義してから、URLのGETメソッドを実行します。このようにすることで、実行時(2.実行)にuseCaseが呼び出され、検証が行われます。*1

私は最初、URLをGETしてから、モックしていました。(//3.検証と//2.実行の順番を反対にしていました。)これでは、最初にURLのGETが実行され、このタイミングでuseCaseも呼び出されますが、この時点ではまだモックしていない(期待することを定義していない)ので、その後にモックをしてテストをしても、1回もhandle()が呼び出されないという判定になってしまいます。プロセスの順序の意味や、内部でどのような処理が行われているかを意識しながらコードを書くことの重要性を、改めて実感しました。

チーム開発/コミュニケーション

コーディング規則

部署内ではコーディング規則があり、linterやformatterにより、保守運用等を伴う変更容易性や可読性を担保しています。加えて、フレームワーク移行のプロジェクトでは、ディレクトリ構成も決められていました。例えば、ビジネスロジックを処理するクラスが明確に分けられていることで、Laravelにも依存しない設計になっていました。また、Bladeファイル(テンプレートエンジン)においてViewModelを活用することで、HTMLで利用可能な変数を渡し、Bladeファイルの修正が必要になるケースを抑えることができることを学びました。実際にPRの修正等で、コードを直す場面がたくさんありましたが、このようなコーディング規則やディレクトリ構成により、変更の容易性を実感しました。

チーム開発する上での細かな注意点

例えば、

// TODO: 以下はサンプルのため移植前に削除する
$this->app->singleton(SampleUseCaseInterface::class, SampleUseCase::class);

新たなコードをこのコメント文の下に追加すると、新たに追加した箇所も削除対象扱いされかねません。チーム開発をする上では、このような箇所にも注意する必要があるということを学びました。

コミュニケーション

インターンを通して、メンターの方々への質問の仕方については課題を感じました。インターンを振り返ると、特に最初の頃は、質問の粒度が細かく、自分でデバッグしたり、他のコードを注意深く確認する前に質問をしてしまったり、「これで合ってるのかな」と心配になることがあって、すぐに確認してしまっていました。また、タスクにも期限が存在するので、自分で調べて解決できるのか、聞いた方が良いのか、という判断が困難でした。

特にアンケートページの回答を入力する処理では、入力データがどのように処理されるのかが複雑に感じ、わからないことが特に多かったので、疑問点や解決したいことをメモ等にまず書き出し、わからないことを定期的に整理することを心がけていました。その中で他のコードを参照したり、調べたりして、疑問点が生じた場合に、聞くようにしていました。その中で、質問を重ねていくうちに、どのように調べれば自分が知りたいことを理解できるのか、デバッグを使用した解決方法についても学ぶことができました。このインターンシップで学んだ質問の粒度や解決能力を、これから様々な場面で、意識していきたいです。

最後に

メンターの方々をはじめ、インターンシップで関わってくださった皆様に、この場を借りてお礼申し上げます。開発だけでなく、人事や上長と面談をさせていただいたり、エキサイト社内イベント「BeerBash」にもご招待いただき、社員の方と交流し、会社の雰囲気を肌で感じることができました。技術的な知識はもちろん、チーム開発で求められることを学び、自分の中で新たな課題点や発見もあり、エキサイトでのキャリアイメージやエンジニアとしての解像度を十分に上げることができました。また、最初は「自分に開発ができるのだろうか」というような不安感もありましたが、自信を持つこともできました。

約1ヶ月という短い期間でしたが、充実したインターンシップとなりました。エキサイト株式会社の皆様ありがとうございました!

*1:shouldHaveReceived()を使用する場合は順番が反対になるそうです。

direnvでプロジェクト・リポジトリごとの環境変数を統一する

エキサイト株式会社エンジニア佐々木です。Web業界ですと複数プロジェクトやリポジトリがあることがほとんどだと思いますが、CLIで作業するときに環境変数に頼りたくなるときがあります。それをディレクトリ単位で制御してくれるdirenv をご紹介します。

インストール

公式にインストール手順がありますので、こちらを参考にします。

direnv/docs/installation.md at master · direnv/direnv · GitHub

Macだと下記のコマンドになります。

## brew経由
brew install direnv


## バイナリインストール
curl -sfL https://direnv.net/install.sh | bash

インストール後に、.bashrc.zshrc に下記の記述する

## bash
eval "$(direnv hook bash)"

## zsh
eval "$(direnv hook zsh)"

対象ディレクトリに .envrc を設置する

対象ディレクトリに.envrcを設置し、必要な環境変数を記載しておく

cd ${対象ディレクトリ}
touch .envrc
vi .envrc

"""
export TAG=latest
export AWS_PROFILE=xxxx
"""

※環境変数 でシークレット情報を扱っている場合は.gitignoreに記載して、コミットしないように注意

記載したら、direnvに許可する必要があるので、下記のコマンドを投入します。

direnv allow

これで設定が完了です。

確認

対象ディレクトリに入ると、.envrc が実行されているのがわかります。

cd ${対象ディレクトリ}

direnv: loading ~/${対象ディレクトリ}
direnv: export +AWS_PROFILE +TAG ~XPC_SERVICE_NAME

まとめ

複数人で開発作業をしているときに環境変数で制御する必要があるときにREADME.mdに書いたり、オンボーディングのときに周知したりで結構面倒なので、これで統一できるなら割と便利になります。

さいごに

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com

Laravelのアップデートを手助けしてくれる「rector-laravel」を使ってみる

いつものtaanatsuです。
今回は、Laravelのアップグレードって大変だなーと思っていたらrector-laravelなるものを発見しました。 github.com

なので少し触ってみようと思います。

rector-laravelとは?

rector-laravelはrectorの拡張で、 rectorとはPHPのコードをアップグレードし、リファクタリングしてくれるもののようです。

nnahito.com

なので、Laravelのアップグレードを補佐してくれるツールですね。

触ってみる

プロジェクトへのインストール

composerでプロジェクトに追加します。
このときに--devをつけて、本番には適応されないようにします。

$ composer require --dev driftingly/rector-laravel

定義ファイルの作成

rector.phpをプロジェクト内に作成します。
中身としては以下です。

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use RectorLaravel\Set\LaravelLevelSetList;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->sets([
        LaravelLevelSetList::UP_TO_LARAVEL_110,        // Laravel11にアップデートするのでこの定数
    ]);
};

リファクタの実行

以下のようなコマンドを実行します。

$ vendor/bin/rector process ディレクトリ名

例えばappディレクトリ配下に適応する場合は以下になります。

$ vendor/bin/rector process app

実行すると解析が行われ、自動的にリファクタしてくれます。

vendor/bin/rector process app
 79/79 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
1 file with changes
===================

1) app/Models/User.php:39

    ---------- begin diff ----------
@@ @@
     /**
      * The attributes that should be cast.
      *
-     * @var array<string, string>
+     * @return array<string, string>
      */
-    protected $casts = [
-        'email_verified_at' => 'datetime',
-    ];
+    protected function casts(): array
+    {
+        return [
+            'email_verified_at' => 'datetime',
+        ];
+    }
 }
    ----------- end diff -----------

Applied rules:
 * AddArgumentDefaultValueRector
 * ModelCastsPropertyToCastsMethodRector
 * StringToClassConstantRector


                                                                                                                        
 [OK] 1 file has been changed by Rector

今回のPHPのコードはLaravelを利用しておりますが、
極力Laravelへの依存度を少なくする方針でコード書いているため、リファクタの範囲は既存のUser.phpだけでした。

SourceTreeで見てみると、リファクタが自動で実行され差分が出ていることもわかります。

このツールがどのくらい業務に耐えうるかはまだまだわかりませんが、少しずつ触っていこうと思いますので、また続報がありましたら更新いたします。
それでは今回はこのへんで。

【Flutter】スクロール挙動をカスタマイズするphysicsについて

こんにちは、エキサイトでアプリエンジニアをしている岡島です。 今回は、ListViewなどスクロールができるウィジェットのphysicsついてまとめていこうと思います。

Scrollableなウィジェット

Flutterには、スクロール可能なウィジェットがいくつか用意されています。主なものには以下があります:

  • ListView
  • PageView
  • GridView
  • SingleChildScrollView
  • CustomScrollView

これらのウィジェットは、physicsプロパティを持っており、このプロパティを通じてスクロールの挙動をカスタマイズすることができます。

physicsについて

physicsプロパティは、スクロールの動作や効果を制御しています。physicsには以下のような種類があるのでそれぞれの特徴について紹介していきます。

  • AlwaysScrollableScrollPhysics
  • BouncingScrollPhysics
  • ClampingScrollPhysics
  • NeverScrollableScrollPhysics
  • RangeMaintainingScrollPhysics

AlwaysScrollableScrollPhysics

AlwaysScrollableScrollPhysicsは、リストがスクロール可能な要素の数にかかわらず、常にスクロール可能にする設定です。、リスト項目が少なくても、スクロール可能な状態が維持されます。

iOSではBouncingScrollPhysicsの挙動、AndroidではClampingScrollPhysicsの挙動となります。

AlwaysScrollableScrollPhysics class - widgets library - Dart API

デフォルト AlwaysScrollableScrollPhysics

BouncingScrollPhysics

BouncingScrollPhysicsは、iOS風のバウンス効果を持つスクロール動作を提供します。リストの先頭や末尾に到達すると、バウンドするようなスクロールです。

BouncingScrollPhysics class - widgets library - Dart API

iOS Android

ClampingScrollPhysics

ClampingScrollPhysicsはAndroidのスクロール動作に近く、リストの先頭や末尾に到達した際に、バウンドせずにスクロールが停止する挙動です。

ClampingScrollPhysics class - widgets library - Dart API

iOS Android

NeverScrollableScrollPhysics

NeverScrollableScrollPhysicsは、スクロールを完全に無効化します。

NeverScrollableScrollPhysics class - widgets library - Dart API

iOS Android

RangeMaintainingScrollPhysics

RangeMaintainingScrollPhysicsは、スクロール範囲が増減しても、自動的にスクロール位置を調整します。 リストの内容が動的に変化する場合に役に立ちます。

RangeMaintainingScrollPhysics class - widgets library - Dart API

最後に

今回はスクロールができるウィジェットのphysicsについてまとめてみました。状況に応じて使い分けることや組み合わせることで、ユーザー体験の向上につながるかと思います。読んでいただきありがとうございました!

Tailwind CSSで連続する要素で交互に適用されるスタイリングをする方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、Tailwind CSSで連続する要素で交互に適用されるスタイリングをする方法として、odd:even: Modifierをご紹介します。

交互に適用されるスタイリングの例

冒頭、連続する要素で交互に適用されるスタイリングをしたい場面を整理します。

よくある例の一つに、以下のような行の背景色が交互に変わるテーブルがあります。

行の背景色が交互に変わるテーブルの例

従来のCSSでは、疑似要素である:nth-child()を使用することで実現できました。

引数に奇数番目の場合はodd、偶数番目の場合はevenを指定すると、連続する要素で交互に適用されるスタイルを作成できます。

tr: {
  &:nth-child(even) {
    background: #EEEEEE; // 偶数番目の兄弟要素の背景色が#EEEEEEとなる
  }
}

Tailwind CSSで実現する方法

Tailwind CSS:nth-child(odd)または:nth-child(even)を表現するには、Modifierであるodd:even: を使用します。

先の例をTailwind CSSでスタイリングすると以下のようになります。

<tr class="even:bg-[#EEEEEE]">...</tr>

Tailwind CSSでは、他にも:first-child:nth-last-child()といった疑似要素を表現するためのModifierが用意されています。(詳しくは公式ドキュメントをご覧ください)

さいごに

今回は、Tailwind CSSで連続する要素で交互に適用されるスタイリングをする方法として、odd:even: Modifierをご紹介しました。

Tailwind CSSを使用している方の一助となれば幸いです。

ご精読ありがとうございました。

【Flutter】RadioListTileのラジオボタンとテキストの間隔を調整する

こんにちはエキサイトでアプリエンジニアをしている岡島です。今回はRadioListTileを用いた時、ラジオボタンとテキストの間隔を調整したい時にどうすればいいかについて共有しようと思います。日本語での記事が無かったので、皆様のお役に立てれば幸いです。

RadioListTileについて

RadioListTileは、ラジオボタンの横にテキストを表示させリスト形式で表示することができる便利なウィジェットです。

RadioListTile class - material library - Dart API

以下のサンプルコードのようにリストで保持した情報を表示することができます。 サンプルコードの詳細はこちらの記事を参考にしてください。

FlutterでHookWidgetを使用してRadioListTileを作成しよう! - エキサイト TechBlog.

class MyHomePage extends HookWidget {
  const MyHomePage({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final sampleList = [
      'サンプル1',
      'サンプル2',
      'サンプル3',
      'サンプル4',
      'サンプル5',
    ];

    final selectedIndex = useState(0);

    return Scaffold(
      appBar: AppBar(
        title: const Text('ラジオボタン'),
      ),
      body: ListView.builder(
        itemCount: sampleList.length,
        itemBuilder: (_, index) {
          final sampleItem = sampleList[index];
          return RadioListTile(
            title: Text(sampleItem),
            value: index,
            groupValue: selectedIndex.value,
            onChanged: (int? value) {
              selectedIndex.value = index;
            },
          );
        },
      ),
    );
  }
}

ラジオボタンとテキストの隙間を調整する

以下のサンプルコードのようにThemeウィジェットを利用して、 ListTileThemehorizontalTitleGapプロパティを使用することで隙間を調整できます。 RadioListTileはListTileThemeを使用しているためListTileThemeを変更するとRadioListTileにも反映されます。

ListTileTheme, which can be used to affect the style of list tiles, including radio list tiles. (RadioListTile class - material library - Dart API参照)

return Theme(
  data: Theme.of(context).copyWith(
    listTileTheme: const ListTileThemeData(
      horizontalTitleGap: 0,
    ),
  ),
  child: RadioListTile(
    title: Text(sampleItem),
    value: index,
    groupValue: selectedIndex.value,
    onChanged: (int? value) {
      selectedIndex.value = index;
    },
  ),
);

添付写真は左側が通常のRadioListTile、右側はhorizontalTitleGapを0に設定したRadioListTileです

左:調整前 右:調整後

さいごに

今回はRadioListTileウィジェットを用いた時の、ラジオボタンとテキストの隙間を調整する方法をご紹介しました。日本語の記事でこのような方法を紹介しているものがなかったので記事にしてみました。この記事がお役に立てれば幸いです。

紙のデザインは必ずラスタライズ!

こんにちは!エキサイトお悩み相談室でデザイナーをしているサヅカです。

9月になっても暑い日々ですが溶けてなんていられません…!今月も張り切ってデザインしていきます!

レイヤー上では透けていないのに背景が透過している!?

以前展示会用にパネルを作った時の話ですが、実は印刷トラブルがありましたので紹介させていただきます。

A1サイズのパネル印刷が届いたので早速チェックしていると…透けている!イラレデータは透過させていないのになぜ…?

背景の黄色い椅子が透けている状態

さらにドットのパターンが網目状に

また、パラペット(ブースの上部看板)に使用したイラストの服のドットにパターンの線が残っていました。 集合体恐怖症としては心臓バクバクです(`;ω;´)

近くで見るとはっきり見えてしまうパターンの線

ラスタライズが必要だった

順調に思われた展示会準備、ここにきて暗雲が…

すぐに印刷会社に電話して、確認後に折り返しが来ました。

「ラスタライズされていないようなのですが、入稿前にラスタライズ処理されましたか?」

「え?」

「え?」

「すいません、してないです…」

「印刷事故を防ぐために、必ずラスタライズという処理をしていただきたいのです」

「!!」

…なんということだ……

知らなかったので丁寧に教えていただきました。

ラスタライズとは、ベクトル画像をピクセル画像に転換し、画像データを軽量化することです。 効果や機能は印刷時にうまく読み込むことができず、色が変わってしまったり変な線が入ってしまったりと思いもよらない印刷の仕上がりにつながります。

↑まさにこれですね。

反省

デザイナー歴10数年、印刷物のデザイン〜入稿まで何度かやってきていますが、お恥ずかしながらラスタライズして入稿は一度も行ったことがありませんでした。

今まで運良く変な仕上がりにならなかったか、あるいは印刷会社さんが気を遣って対応してくださっていたんだなと思います(申し訳なさ極まりない)

今後は必ずラスタライズするようにして印刷事故を防ぎたいと思います(`;ω;´)

Tailwind CSSでタグのベーススタイルを設定する方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、Tailwind CSSでタグのベーススタイルを設定する方法をご紹介します。

実現したいこと

冒頭、実現したいことを整理します。

例えば、サイト内のh1タグのスタイルをすべて共通にしたい場合、カスタムクラスを用意するという手があります。

@layer components {
  .heading-1 {
    @apply font-body text-2xl
  }
}
<h1 class="heading-1">文字には「font-body text-2xl」が適用される</h1>

しかし、このアプローチの方法ですと、毎回クラスを呼び出す必要があったり、そもそもこのクラスの存在自体を周知する必要が生じます。

そこで、従来のCSSのようにクラスを使用せずにタグに対して直接スタイルを記述して、クラス指定なしスタイリングできるようにしたいです。

h1 {
  @apply font-body text-2xl
}
<h1>文字には「font-body text-2xl」が適用される</h1>

@layer baseでベーススタイルを設定できる

タグのベーススタイルを設定したい場合、Tailwind CSSを読み込んでいるCSSファイルに@layer baseカスケードレイヤーを追加すると設定ができます。

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  h1 {
    @apply font-body text-2xl
  }
}

これでクラス指定なしスタイリングが可能になりました。

さいごに

今回は、Tailwind CSSでタグのベーススタイルを設定する方法をご紹介しました。

Tailwind CSSを使っている方の一助となれば幸いです。

ご精読ありがとうございました。

Alpine.jsでページ読み込み時に非表示要素が一瞬見えてしまう「blip」を防止する方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はAlpine.jsを使用している環境下で、ページ読み込み時に隠している要素が一瞬表示されてしまう「blip」という現象を防止する方法をご紹介します。

blipとは

冒頭、Alpine.jsにおけるblipとはなにかを整理します。

例えば以下の場合、pタグにはx-showディレクティブによって、isOpentrueになると表示されるという条件が設けられています。

したがって、ページ読み込み時には表示されないことが期待されます。

<div x-data="{ isOpen: false }">
  <button @click="isOpen = true">クリック</button>
  <p x-show="isOpen">ボタンをクリックしたら表示される文字</p>
</div>

しかし、ページが読み込まれた後、Alpine.jsの読み込みが完了する前の一瞬、初期化されていないテンプレートが表示されてしまうことがあります。

これを「blip」と呼びます。

どうやってblipを防止するか

blipは、ページ読み込み時には隠れているべき要素にx-cloakディレクティブを付与することで防止できます。

先の例ですと、ページ読み込み時には隠れているべき要素はpタグですので、以下のようにx-cloakを付与します。

<p x-cloak x-show="isOpen">ボタンをクリックしたら表示される文字</p>

ただし、これだけでは機能しません。必ず、CSSに以下のスタイルの追加が必要です。

[x-cloak] { display: none !important; }

これによって、x-cloakが付与された要素はAlpine.jsの読み込みが完了するまでdisplay: none;が適用され、blipの発生を防ぎます。

さいごに

今回はAlpine.jsを使用している環境下で、ページ読み込み時に隠している要素が一瞬表示されてしまう「blip」という現象を防止する方法をご紹介しました。

Alpine.jsを使用されている方の一助となれば幸いです。

ご精読ありがとうございました。