うちのサイトて、ウェブアクセシビリティを確保できていますか?

こんにちは。エキサイト株式会社のあはれんです。

エキサイトホールディングス2023アドベントカレンダー 22日目をお届けします。

qiita.com

「法律が改正されるから、サイトのウェブアクセシビリティを気にしたほうがいい」というのを風の便りに聞きました。

私はこれまで「ウェブアクセシビリティ」という言葉を耳にしたことはある程度でしたが、具体的な内容については理解が不十分でした。

そこで調査を行いました。 以下にウェブアクセシビリティに関する知見を共有いたします。

ウェブアクセシビリティてなに?

ウェブアクセシビリティとは、利用者の属性(障がいの有無、年齢、利用環境)に関わらず、すべての人がウェブサイトを利用できることを意味しています。

ウェブアクセシビリティは、障がい者の方向けの対応だけではありません。

荷物を持っており手が塞がっている状態でもウェブサイトを利用可能にすることも含まれます。

ウェブアクセシビリティは努力義務

令和3年(2021年)に、障害者差別解消法(障害を理由とする差別の解消の推進に関する法律)が改正され、国や地方公共団体などに義務付けられている合理的配慮の提供が、民間の事業者も義務化されることになり、令和6年(2024年)4月1日に施行されます。

:

その合理的配慮を的確に行うため、環境の整備が努力義務となっており、ウェブサイトの場合ではJIS X 8341-3:2016に準拠したウェブサイトを作り、ウェブアクセシビリティを確保することがこれに当たります。

引用URL:ウェブアクセシビリティとは? 分かりやすくゼロから解説! | 政府広報オンライン

障害者差別解消法は改正されますが、ウェブアクセシビリティは以前同様に努力義務のままです。

努力義務ではありますが、提供するウェブサイトがすべての人に使ってもらえる状態が理想ですので、ウェブアクセシビリティの確保は重要なことです。

ウェブアクセシビリティを確保するには、 JIS X 8341-3:2016 に準拠したウェブサイトを制作する必要があるとのことですが、 JIS X 8341-3:2016 とはなんでしょうか?

ウェブアクセシビリティガイドライン「JIS X 8341-3:2016」とは?

「JIS X 8341-3:2016」の正式名称は、『高齢者・障害者等配慮設計指針-情報通信における機器,ソフトウェア及びサービス-第3部:ウェブコンテンツ』 と言います。

「JIS X 8341-3:2016」は、ウェブアクセシビリティの確保を目的して、ウェブサイトが満たすべきアクセシビリティの品質基準として、「レベルA」「レベルAA」「レベルAAA」という3つのレベルの達成基準が定められています。

例えば、レベルA には「達成基準 2.4.2 ページタイトル」があり、以下のことが書いてあります。

達成基準: ページタイトル: ウェブページには、主題又は目的を説明したタイトルがある。

意図:各ウェブページにその内容を示すページタイトルを付けることによって利用者がコンテンツを見つけやすくするため

事例:HTML で制作したウェブページの内容を示したタイトルが、ユーザエージェントのタイトルバーに表示されるようにマークアップする

引用URL:達成基準 2.4.2 を理解する | WCAG 2.0解説書

他にはどのような達成基準があるか知りたい場合は、レベルA、レベルAAの達成基準 早見表がおすすめです。 以下のURLを参照ください。

JIS X 8341-3:2016 達成基準 早見表(レベルA & AA)

ウェブアクセシビリティに対応したウェブサイトにするにはどうすればいいの?

「JIS X 8341-3:2016」で、ウェブアクセシビリティの達成基準が定義されているので、この達成基準を満たしたウェブサイトを用意すればいいのです。

と、言うのは簡単ですが、実際には基準を達成させるのは難しいです。 ですので、ウェブアクセシビリティを高めるプロセスとして、以下の流れが推奨されています。

  1. ウェブアクセシビリティの方針策定
  2. アクセシブルなウェブコンテンツの制作
  3. 試験の実施と結果の公開

1. ウェブアクセシビリティの方針策定

「適用範囲」「目標とする適合レベル及び対応度」を策定します。 実際に公開しているサイトを見てみると、どのようなものかイメージがつきやすいと思います。

www.ajinomoto.co.jp

www.kao.com

2. アクセシブルなウェブコンテンツの制作

ウェブアクセシビリティ方針において定めた目標を達成できるように、ウェブコンテンツを制作します。 制作すると言っても、まず達成基準を知る必要があります。それには以下の解説集が参考になります。

waic.jp

また、達成すべきことはわかっても、実装する方法はわからないよ。というときには、以下の達成方法集が参考できます。

waic.jp

3. 試験の実施と結果の公開

制作したコンテンツが、目標達成できているのか試験し、その結果を公開します。 ウェブアクセシビリティの試験のチェックツールは存在しますが、チェックツールで確認できるのは達成基準の2〜3割程度だそうです。 試験は、必ず人が目視による確認や操作が必要とされています。

詳細な試験の進め方は、以下のサイトが参考になります。

waic.jp

また、実際に公開しているサイトを見てみると、どのようなものかイメージがつきやすいと思います。

www.ajinomoto.co.jp

www.digital.go.jp

うちのサイトて、ウェブアクセシビリティ確保できていますか?

ウェブアクセシビリティや、ウェブアクセシビリティに対応したウェブサイト制作のプロセスについて知ることはできました。

試しに、弊社のサービスがウェブアクセシビリティを確保できているのか簡単にチェックしてみたいと思います。

ツールでのチェック

ウェブアクセシビリティでのチェックツールはいくつかありますが、 今回は、ChromeLighthouseを利用しました。

Lighthouseの使い方は以下のページを参考ください。

developer.chrome.com

以下が検査結果のページになります。

Ligthouseでのアクセシビリティテスト結果サムネイル

googlechrome.github.io

結果は90点で、指摘項目は以下になります。

  • 背景色と前景色のコントラスト比が不十分です。
  • リンクには識別可能な名前がありません
  • 画像要素には、冗長テキストである [alt] 属性があります。

手動でのチェック

今回は、以下の達成基準についてチェックします。

キーボード操作だけで、サービスのすべての機能にアクセスすることができるようにする

・キーボード操作時に、フォーカスインジケーター(選択中の要素を枠線等で囲んで示すこと)が表示されるようにしましょう

・キーボード操作時に、フォーカス・入力がキャンセルされたり、フォーカス・入力した瞬間に何かが勝手に動作することがないようにしましょう

引用url :https://www.digital.go.jp/assets/contents/node/basic_page/field_ref_resources/08ed88e1-d622-43cb-900b-84957ab87826/9f89625f/20230512_introduction_to_weba11y.pdf

以下の動画で、Tabキーでページを巡回しEnterキーでリンク移動操作をすることを確認できました。

Tabキーでページを巡回しEnterキーでリンク移動操作している動画
Tabキーでページを巡回しEnterキーでリンク移動操作している動画

以下の動画では、カウンセラーリストのフォーカスインジケーターが見えづらくなっていることが確認されました。

フォーカスインジケーターが見えづらくなっている動画
フォーカスインジケーターが見えづらくなっている動画

まとめ

ウェブサイトの提供者として、広く多くの人々に利用していただきたいと考えています。 そのためには、ウェブアクセシビリティの確保が非常に重要です。

今回、ウェブアクセシビリティガイドライン「JIS X 8341-3:2016」を調べ、 普段の開発においてアクセシビリティに十分な意識が行き届いていなかったことに気づきました。

知識不足も一因でしたが、目が使わずウェブサイトを見るなどの通常よりアクセスしづらい状況での利用ケースを想像できていませんでした。

ウェブアクセシビリティを向上させるには、既存のガイドラインを学びつつ、 様々なユーザーの利用ケースを想像し、実際にアクセスが容易かどうかを検証しながら開発していく必要があると感じました。

すぐにウェブアクセシビリティを達成することは難しいかもしれませんが、学びながら着実に改善を進めていきたいと考えています。

ウェブアクセシビリティについて学ぶ際に参考になるサイトとして、以下の2つをご紹介いたします。是非ご覧いただければと思います。

waic.jp

www.digital.go.jp

「サムネイル制作」で学んだこと- 第2弾-

こんにちは!デザイナーとして内定者アルバイトをしている澤田です! エキサイトホールディングス Advent Calendar 2023 シリーズ2の21日目の記事です!

qiita.com

1年ぶりにサムネイル制作についてまとめてみる

昨年夏にインターンシップでサムネイル制作を任せていただきました! 改めて読み返してみて「もしかしたら少しだけ成長できたかも...?」と思ったので、改めて同じ題材をまとめてみようと思います。 お恥ずかしいですが1年前の私の記事はこちらです...!

tech.excite.co.jp

今回は、先月に行われたエンジニア採用イベントでのサムネイル制作を通じて学んだことを共有したいと思います。 このイベントはエンジニア志望の学生が多く登録している就活サイト「LabBase」での単発のイベントになります。

まずはユーザー理解することが重要!

最初に取り組んだことは、サムネイルの対象となる学生像を明確にすることでした。採用担当の方が口にしていた言葉や就活生たちのTwitterでのつぶやきを参考にしながら、就活生と採用側のニーズを言語化しました。これにより、後の市場調査やイメージボード制作時にどのキーワードから検索をかけるべきか、またどんなデザインが参考になるかというサムネイル作りの足掛けができました。

サムネイルを通して関わる二人の人物像

イメージボードはアイデアを増やすためにも必要!

ユーザー像をクリアにしていく中で「エキサイトらしさってなんだろう?」という壁に当たりました。そこで、先輩デザイナーの方々が以前にエキサイトホールディング株式会社のサイトを作成する際に用いたキーワードを参考にしました。そのキーワードを参考にピンタレストで魅力的なサムネイルを見つけ、これらをカテゴリー別に分類しマッピングしました。また見つけてきたサムネイルがどのような要素を含んでいるのか言語化することで、いつもより多くのデザインアイデアが思いつきました。

集めたサムネイルをマッピングし要素を言語化

実際のサイズで確認しながらレイアウト調整する!

画面いっぱいに広げて制作していたため、実際のサイズで確認すると、文字が小さく読みづらいなどの問題が見えてきました。そこで、Figmaを使って実際に掲載されるサイトを再現し、文字の読みやすさや他のサムネイルと比較し魅力的に見えるかどうかを何度も確認しました。 またフィードバックいただく際もこのFigmaリンクを共有することで、相手の方にも実際のイメージを共有しながらアドバイスをいただくことができました。

確認前と最終提案のデザイン比較

最後に

執筆後に改めて1年前の自分と比較してみると、昨年にはなかった視点で制作できている...!と思いました。このような学びの多い環境にいられることにありがたい...と思うと同時にさらなる成長を目指していこうと思いました。最後まで読んでいただき、ありがとうございました。

GitHub Actionsのworkflow_callイベントを使用してワークフローファイルを分割する

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣(@zsp2088dev)です。

エキサイトホールディングス Advent Calendar 2023の6日目を担当します。

私の担当するエキサイトブログでは、アプリケーションのテスト、ビルド、デプロイにGitHub Actionsを使用しています。 GitHub Actionsを使用するにあたり、ワークフローファイルを.github/workflowsに定義していますが、 初回に定義したワークフローファイルのメンテナスが中々できていませんでした。 このような中で、GitHub Actionsでワークフローを再利用できることを知り、これを適用できそうだと判断し、実際にワークフローファイルを分割することにしました。

本記事では、workflow_callイベントを使用したワークフローの分割とその際にハマった箇所、今後取り組みたいことについて紹介します。

概要

ワークフローファイルを分割する前のファイル一覧です。

❯ tree .github/workflows 
.github/workflows
├── deploy_production.yml
├── deploy_staging.yml
└── deploy_test.yml

それぞれのワークフローファイルで、以下の処理を定義しています。 このように、共通の処理が多く存在し、コピペでワークフローファイルを作成することが常態化していました。

テスト環境

  1. コンテナをビルド
  2. Amazon ECRにビルドしたイメージをプッシュ
  3. Amazon ECSにデプロイ

ステージング環境/本番環境

  1. 静的ファイル(CSS/JavaScript)をAmazon S3にアップロード
  2. コンテナをビルド
  3. Amazon ECRにビルドしたイメージをプッシュ
  4. Amazon ECSにデプロイ

ワークフローファイルの分割

GitHub Actionsでワークフローを再利用できることを知り、早速ワークフローファイルを分割することにしました。 ワークフローを再利用するには、workflow_callイベントを使用します。 これにより、別のワークフローからワークフローを呼び出すことができるようになります。

docs.github.com

以下に分割後のワークフローファイルの一例を示します。

今回のケースでは、workflow_dispatchイベントからworkflow_callイベントを呼び出しており、静的ファイルのアップロードとコンテナデプロイの処理を、それぞれworkflow_callイベントに切り出しました。 これにより、それぞれの処理が一箇所にまとまり、タスクの追加や修正の手間を低減することができるようになります。

静的ファイルのアップロード (upload_to_s3.yml)

on:
  workflow_call:
    inputs:
      aws-s3-path:
        type: string
        required: true
    secrets:
      sample-secret-value:
        required: true

jobs:
  upload-to-s3:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
       # 以下で、静的ファイルのアップロード処理

コンテナデプロイの処理 (deploy_to_ecs.yml)

on:
  workflow_call:
    inputs:
      image-name:
        type: string
        required: true
      cluster-name:
        type: string
        required: true
      service-name:
        type: string
        required: true
    secrets:
      sample-secret-value:
        required: true

jobs:
  deploy-to-ecs:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      # 以下で、コンテナビルド→コンテナデプロイの処理

テスト環境のデプロイ (deploy_test.yml)

name: 'deploy test'
on:
  workflow_dispatch:

jobs:
  skip-upload:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Skip upload"

  deploy:
    needs: skip-upload
    uses: ./.github/workflows/deploy_to_ecs.yml
    with:
      image-name: sample-test
      cluster-name: sample-test-cluster
      service-name: sample-test-service
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_TEST }}

ステージング環境のデプロイ (deploy_staging.yml)

name: 'deploy staging'
on:
  workflow_dispatch:

jobs:
  upload:
    uses: ./.github/workflows/upload_to_s3.yml
    with:
      aws-s3-path: s3://sample/staging/path
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_STAGING }}

  deploy:
    needs: upload
    uses: ./.github/workflows/deploy_to_ecs.yml
    with:
      image-name: sample-staging
      cluster-name: sample-stating-cluster
      service-name: sample-staging-service
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_STAGING }}

本番環境のデプロイ (deploy_production.yml)

name: 'deploy production'
on:
  workflow_dispatch:

jobs:
  upload:
    uses: ./.github/workflows/upload_to_s3.yml
    with:
      aws-s3-path: s3://sample/production/path
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_PRODUCTION }}

  deploy:
    needs: upload
    uses: ./.github/workflows/deploy_to_ecs.yml
    with:
      image-name: sample-production
      cluster-name: sample-production-cluster
      service-name: sample-production-service
    secrets:
      sample-secret-value: ${{ secrets.SAMPLE_SECRET_VALUE_PRODUCTION }}

ハマった箇所

workflow_callイベントを使用するにあたり、ハマった箇所について2つ紹介します。

stepsでワークフローファイルを呼び出すとエラーになる

再利用可能なワークフローを呼び出すときに、steps内で呼び出していたところエラーになりました。 jobs内で呼び出さないといけないようです。

以下の通り、公式ドキュメントの制約事項にも記述がありました。

Reusable workflows are called directly within a job, and not from within a job step. You cannot, therefore, use GITHUB_ENV to pass values to job steps in the caller workflow.

1つだけ別のワークフローを呼び出すとエラーになる

テスト環境のデプロイで使用するワークフローには、skip-uploadジョブを定義しています。 これは、以下のエラー文の通り、workflow_callイベントのみを使用したジョブを定義することができないため使用しています。

The workflow must contain at least one job with no dependencies.

今後取り組みたいこと

ワークフローファイルで、workflow_callイベントを使用したワークフローファイルの分割以外にも取り組みたいことについて、以下にまとめます。

Environments secretsの活用

GitHub Actionsでは、Environments欄より環境を設定できます。 さらに、環境ごとに環境変数とシークレット変数を定義できます。

現状では、リポジトリ単位でシークレット変数を定義しており、変数名の末尾に_TEST_PRODUCTIONといった環境名を定義しています。 Environments secretsを活用して、それぞれの環境ごとにシークレット変数を用意したいと考えています。

docs.github.com

on.workflow_dispatch.inputsの活用

workflow_dispatchイベントを使用して手動実行するときに、inputsを使用することで、セレクトボックスを配置できます。 例えば、以下のように環境名を選択肢に入れることで、その値をワークフローファイル内で使用することができます。 また、上記のEnvironments secretsと併用することで、より使いやすいワークフローファイルを定義できそうです。

on:
  workflow_dispatch:
    inputs:
      ENV:
        description: 環境を指定します
        default: test
        required: true
        type: choice
        options:
          - test
          - staging
          - production

docs.github.com

おわりに

本記事では、workflow_callイベントを使用したワークフローの分割とその際にハマった箇所、今後取り組みたいことについて紹介しました。 エキサイトブログでは、既存機能の改修や新機能の開発などに取り組みやすい環境を作るために、CI/CDの整備にも力を入れていきます。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。

カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

▼ 募集職種一覧 ▼ recruit.jobcan.jp

デザイナーが今年買った本を振り返る

こんにちは!SaaS・DX事業部デザイナーの鍜治本です!
エキサイトホールディングス Advent Calendar 2023 シリーズ2の20日目の記事です!

qiita.com

新卒で入社してから早三年。仕事も慣れた部分や生活環境が変わったこともあり、例年に比べて本を読む機会が多くありました。
ペースが遅く1冊を読みきれないことも多々あるので、しっかりとした紹介はできませんが、購入した本をいくつかご紹介します。

今年購入した本一覧

購入した本のうち、仕事柄関連していそうな本をリストアップしてみました。リンクも記載したので、もし興味がある本があればぜひ読んでみてください🙆

2023年に購入した本たち

読み終えた本・読みきれなかった本

読み終えて二週三週と繰り返し読んでいる本もあれば、冒頭に記載した通り、読みきれなかった本も入っています。
詳しい内容までは触れませんが、読み終えた本とそうでなかった本の特徴に触れ、それぞれ一言程度でご紹介します。

読み終えた本

こちらの4冊は、読み終えたり読み返している本です。

左から、「銀行とデザイン」「ちいさくはじめるデザインシステム」「決算書が読めるようになる!」「会計の地図」

左の2冊はデザインに関連しており、実務レベルの事柄や事例を紹介されています。「銀行とデザイン」は、大手金融業界に変革をもたらしたデザインと、デザインを浸透させた過程・課題解決についてふんだんに盛り込まれています。業界は違えど、デザイナーがプロダクト・サービスに対して何ができるか?を改めて認識できた一冊です。
「ちいさくはじめるデザインシステム」は、デザインシステムについて具体的な事柄から抽象的な事柄まで広く描かれており、自分が現在進行形で制作しているUIの参考にもしました。実務と重ねて考えられ、読み進める上でも納得感を持ちながら読めた2冊です。

一方、右の2冊は会計や会社の数値にまつわる書籍です。担当しているKUROTENは大きな括りで言えば会計分野、お金周りの知識を増やすべく読み始めました。
馴染みのない用語や実体験のない仕組みにすぐには理解できない部分もあり、前の2冊と比べても読むのに時間を要した覚えがあります。ただ、書籍自体はかなり易しく書かれていたこともあり、周回して少しずつ知識化しています。

読みきれなかった本

反対に、読んでみたものの途中で挫折した本や、完読できなかった本はこちらの3冊。

左から、「Googleアナリティクス4」「デジタル・ブランディング」「思考技術」
左の「Googleアナリティクス4」は、GA4の勉強のために購入したものの、基礎知識がない状態で読むには全くわからずだったため挫折しました。UXをより良くするべく分析方法を学ぶ為だったのですが、まずはGA4自体を触りながら慣れていった方が良いと判断し、現在は積読しています。
中央の「デジタル・ブランディング」は、ブランド構築におけるメソッドやヒントを得るべく読み始めました。書籍の内容は、事例紹介や著書の経験談などで少し冗長だったため、すぐ読む温度感ではないとし、優先度を下げて保留しています。
右の「思考技術」については、考え方・アウトプットの仕方が言語化されており分かりやすいものの、ワークアウト的に進めた方が良い構成だったため積読中です。

業務と関係なく今年買った本

ここからは業務・デザインが関係ない今年購入した本です。おまけ程度でご紹介します。

一目惚れで買った本

左から、「イラストで見るゴーストの歴史」「47㎡、2人暮らし」
イラストで見るゴーストの歴史」はSNSで見かけ、イラストのかわいらしさに一目惚れして購入。世界各国伝承されたゴーストたちや、それらにまつわる事例など、見た目とは裏腹にかなり濃密にまとめられた書籍です。
47㎡、2人暮らし」もSNSで見かけて衝動買いした本で、部屋を作る上で意識すべきポイントを簡単なルールにしてまとめられています。生活環境が変わったので、年末の大掃除で取り入れておしゃれ部屋を目指したい所存です。

完全に趣味で買った本

左から、「未踏の蒼穹」「ガニメデの優しい巨人」「巨人たちの星」「内なる宇宙 上下」
全てSF小説です。学生時代に読んだ「星を継ぐもの」の新版が出るニュースに触発され、ジェイムズ・P・ホーガンの本を一気買い。とんでもなく面白いのですが、私の語彙では説明しきれないので、興味を持った方はぜひ読んでみてください。

おわりに

蛇足が長くなってしまいましたが、デザイナーとして業務で活かせるインプットとして本を読んできました。インプットとして読書を選んでいるのは、知識として蓄積できるもの・今はまだ自分にふさわしくないもの、これらを実際に読まねば分からないワクワク感が好きなのだと感じています。
ちょうど大きい本棚を購入したので、知識を蓄えられるようにたくさんインプット・アウトプットして、本棚をぎゅうぎゅうに埋めたいです。

最後までご覧いただきありがとうございました🙇

初めてのオフライン展示会からデザイナーが学んだこと②

こんにちは!SaaS・DX事業部デザイナーの鍜治本です! エキサイトホールディングス Advent Calendar 2023 シリーズ1の20日目の記事です! qiita.com

2023年11月に出展したBOXIL EXPOについて、展示に向けて準備したことについても記事化していきます〜!
今回はイベント期間中に着用していたスタッフウェアについて、どのようなステップで作成されたかをお伝えします。

スタッフウェアができるまで

今回制作したウェアはエキサイトのロゴかプリントされた黒地のTシャツと、KUROTENのロゴが印刷された白地のTシャツの2種類。

発注前に作成したTシャツのモックアップ
文化祭で定番の『クラスT』を作成したことがある方は分かるかもしれませんが、オリジナルのTシャツを作成できるサービスがあります。
Tシャツのデザインはあらかた決まっており、デザインデータも用意できることから、印刷業者にデザインデータを渡し印刷してもらうプロセスで制作を進めました。

着用するウェアの扱い

これまで、社内カンファレンスやサマーインターンといったイベントでも、オリジナルでTシャツを作成することがありました。エキサイトで開催される『イベントの一部を体感するもの』としてTシャツが求められていたことから、イベントのモチーフをあしらったり、既存のイメージを広げるようなTシャツのデザインを作成していました。

過去に社内カンファレンス(TechCon)で試作していたもの
今回のEXPOでは「エキサイトという企業」として出展しているものの、来場者は「サービス・プロダクト」を見にきているイベントです。そのため、『イベントの一部を体感するもの』ではなく『企業・サービス・プロダクトとしての姿』を示すウェアが求められると考え、サービスロゴやコーポレートロゴのみの質素なスタイルにしました。

デザインデータの作成

作成する目的が決まったら、デザインデータを作成していきます。今回印刷を依頼したクラTジャパンさんはテンプレートを配布しているので、テンプレートの指示に従いデザインを作成します。

テンプレートにKUROTENロゴを配置しています
ロゴをプリントする位置を確認するため、手持ちのTシャツを使って襟ぐりから距離を測ってみたり、着用時のシワが響かない位置を探りながら入稿用デザインを作成しました。

入稿確認と発注

作成したデザインを印刷業者に渡し、プリント前の最終チェックします。
中でもエキサイトロゴの印刷において業者とやり取りを挟んだ箇所が2つあり、それぞれの原因と解決方法について記載します。

エキサイトロゴの色

エキサイトロゴは赤色と白(または黒)の組み合わせで構成されています。この赤色はRGBで指定されている赤色であり、印刷などCMYKで再現される色味とは一致しません。*1

RGBをCMYKにしても、鮮やかさが再現できるわけではない
ロゴの赤色部分はR255 G0 B0のディスプレイで見る鮮やかな赤色です。このまま印刷するとくすんだ赤色になってしまったり、下手に調整すれば朱色のようなオレンジがかった色になりかねません。
今回は時間との兼ね合いもあったため、印刷業者の方とすり合わせた上で、印刷可能なインクからロゴに近しい赤色を選びました。

エキサイトロゴの輪郭

エキサイトのロゴの「x」部分は、筆で書き上げたような筆のかすれが再現された形をしています。勢いがあって個人的に好きな要素ですが、デザインデータにおいてはそうもいきません。
今回の印刷では、ロゴが印刷されたシートをTシャツに貼り付ける手法が取られています。輪郭の形状がこまかくなる程、シートの縁も複雑になり、剥がれやすくなってしまう懸念がありました。
そこで印刷業者の方から、ロゴの縁に沿って輪郭線をつける提案をいただきました。

データに追加された黒い輪郭線
輪郭線を追加し細かな部分を1シートに収めたことで、複雑なシルエットでも印刷しやすい状態にしています。

これらの修正を加えた上でそれぞれのTシャツを発注しています。単にロゴを乗せただけで完成ではなく、目的や細部まで目を配った上で制作できました。

ExciteロゴTシャツの印刷部分をアップで撮影
実際のTシャツを見ても、ディスプレイと大差なく鮮やかさが確保できており、ロゴに追加した縁も悪目立ちしていませんでした。何度も着用するウェアであるからこそ、基準となる数値を完璧に再現よりも、相対的にどうあるべきかを考慮して制作できたと感じています。

イベントで着用してみた結果

ブース内で来場者と話している様子
スタッフウェアを着用することで、企業やサービスを目に入れてもらうのはもちろん、ブースのスタッフとしても分かりやすくなります。
また、今回のようなイベントのみならず、ウェブセミナーやオンライン商談といった場面でも横展開できます。今後も、それぞれのウェアが活躍できる場が増えると嬉しいです。

おわりに

今回は展示会出展で作成したものとして、スタッフウェアにフォーカスした記事をまとめました!これからスタッフウェアやイベントTシャツを作ろうとしている方の参考になれれば嬉しいです~!
さらにエキサイトでは、デザイナーやエンジニア(もちろんビジネスも!)を盛り上げてくれるメンバーを、新卒・中途問わず募集しています! カジュアル面談も随時受付可能ですので、お気軽にお声かけください!🙆

recruit.jobcan.jp

*1:印刷業者によっては色味の再現をできる「特色」という方法で再現できるところもあります。

エキサイト株式会社の採用候補者向け会社説明資料を制作した話【前編】

はじめに

こんにちは、エキサイト株式会社3年目デザイナーの山﨑です。 エキサイトホールディングス Advent Calendar 202316日目は、山﨑が担当させていただきます。

qiita.com

今回はエキサイト株式会社の採用候補者向け会社説明資料をリニューアルした時のお話をしたいと思います。

リニューアル目的とは?

元々エキサイトには会社説明資料があったのですが、「採用したい人材に刺さる会社説明資料を作りたい」「Speaker Deckに投稿されているエキサイトの会社説明資料の文字のあしらいがおかしなことになっているので、直してほしい」というオーダーによってリニューアルすることになりました。

早速Speaker Deckのエキサイト会社説明資料を確認してみると…

文字が袋文字になっている…!?しかも要素から文字がはみ出している…!

袋文字:輪郭線だけがある文字。文字の強調する時によく使われる。

他のデザイナーさんに調べていただいたところ、Googleスライドで「日本語フォント + Bold」で発生するバグのようです。

zenn.dev

対処法としては、下記の方法で解決するようです。

  • Boldを使用しない
  • フォントに「M PLUS 1p」を使用する
  • PowerPointで作る

Googleスライドかつゴシック体を使用して検証していただいたところ、下記フォントで文字の掠れはみられませんでした。

  • Noto Sans JP
  • BIZ UDP Gothic
  • Zen Kaku Gothic New
  • M Plus 1p、M Plus 1p

採用したい人材とは?

Speaker Deckに投稿されているエキサイトの会社説明資料の文字のあしらいがおかしなことになっているので、直してほしい」というオーダーはフォントを変更すれば解決できることがわかりました。

次に「採用したい人材に刺さる説明資料」というオーダーですが…

例えば、X事業部は「高エネルギーでスピード感があり、新規事業に興味がある人を採用したい!」という要望がありY事業部は「堅実で実直に業務を行ってくれる、既存事業の収益を伸ばすことに興味がある人を採用したい!」という要望がある場合、それぞれの採用希望人材に刺さるアウトプットはコンテンツ、デザインともに全く異なります。

事業部ごとに採用資料を作ることはあまりないのですが、今回はかなり採用したい人材の属性が異なっています。

そのため、汎用的に使える、安心・堅実な印象を与える採用資料Aパターンと、採用を強化している事業部をピックアップしその人材に刺さるデザインで、挑戦的な印象を与える採用資料Bパターンを制作することになりました。

加えて、締切にそれほど余裕がなかったので一旦「Aパターン」を作りOKが出たらBパターンを制作しようという方針になりました。

制作ツールは何にする?

制作ツールは、色々な方が編集しやすいようGoogleスライドを採用しました。

ただ、Googleスライドは収録フォントが少なくデザイン性に凝ることが難しいため、会社のミッションなど編集する機会が少ない部分はこぶりなゴシックで打ち込んだものを画像で書き出して貼り付けを行うなど工夫を行いました。

ラフデザインについて

Aパターンを制作するにあたり、採用候補者に「堅実さ、わかりやすさ、安心感」が伝わるよう意識しました。

まず参考企業の採用資料を集めて、どの部分に堅実さや安心感につながるエッセンスが散りばめられているのか模索しながらデザインラフを作っていきました。

以上のような雰囲気が伝わるラフデザインを7ページ×2パターンで計14ページ制作し、役員にデザイン案を決めていただきました。

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひぜひ連絡ください!🙇

www.wantedly.com

デザインシステムを活かすためのドキュメンテーション

こんにちは。エキサイトで内定者アルバイトとしてデザイナーをしている齋藤です。

エキサイトホールディングス Advent Calendar 2023 シリーズ2の19日目は、私齋藤が担当させていただきます。

今回は、あるプロジェクトでデザインシステム構築を担当していた際に、コンポーネントライブラリのドキュメントを書く中で考えていたことやドキュメントに記していたことについてお話したいと思います。

はじめに

冒頭、私がやっていたことをざっくりとご紹介します。

とある新規サービスの立ち上げにジョインし、当初はUIデザインとフロントエンド(主にマークアップ)を担当していました。デザインはFigma、フロントエンドはNuxt.js(v3・Composition API・TypeScript)を用いた開発環境でした。

開発を進める中で、新規機能の追加などの将来的な拡張を見据えて、デザイン面ではデザイン要素のルール化、エンジニアリング面では汎用的なUI要素のコンポーネントライブラリ化をすることで開発スピードの向上や非属人化、一貫性の担保などができると考え、メンターやエンジニアの方々にご相談をした結果、デザインシステムの構築をさせていただくことになりました。

デザインシステムの構築では以下のことをやっていました。

今回は「ドキュメンテーション」に絞ってお話をすることになります。

なぜデザインシステムにドキュメントが必要なのか

なぜデザインシステムにドキュメントが必要なのかを言語化しておきたいと思います。

デザインシステムを構築すると、デザイナー側はFigma、エンジニア側にはコードとして残りますが、どちらも双方に共通するデータとなることは難しいと考えます。

デザインシステム自体、デザインとエンジニアリングをつなげる共通言語的なものを理念としていますので、その成果物が完全に二分されてしまうことは理念に反します。

そこで、デザイナー・エンジニア双方に共通するデータとして、ドキュメントが必要であると私は考えます。

ドキュメントに何を記すか

実際のButtonコンポーネントを例に、どんなことをドキュメントに記していたかをご紹介します。

デザインパターン

存在するデフォルト状態の表層的なデザインのパターンの名称や使用用途を記したセクションです。

実際のドキュメントの一部

使用用途を明示することにより、そのデザインパターンがどのような場合に適するのかについての共通認識を生み出し、デザインの属人化を防ぎます。

状態変化

Hover時やActive時、Disabled状態など、デフォルト状態以外の表層的なデザインの変化を記したセクションです。

実際のドキュメントの一部

Figmaでは状態変化はVariantsで表現することが多いため、その情報も書いておきます。

サイズバリエーション

コンポーネントにサイズのバリエーションがある場合、その詳細な値を記したセクションです。

実際のドキュメントの一部

こちらも、FigmaではVariantsで表現することが多いため、その情報も書いておきます。

コンテンツバリエーション

コンポーネント内の要素の有無(Buttonならアイコンの有無など)が選択できる場合、その旨を記したセクションです。

実際のドキュメントの一部

こちらも、FigmaではVariantsで表現することが多いため、その情報も書いておきます。

Components API

ここからはコーダー向けのセクションです。

Import

コンポーネントのImport文を記します。

実際のドキュメントの一部

Props

コンポーネントにあるPropsの情報を記します。

実際のドキュメントの一部

それぞれのtypePropType、デフォルトの値などを明記します。

加えて、Components APIより前のセクションとの関係や、どういった挙動をするのかなども書いておきます。

Slot

コンポーネントにSlotが存在する場合、どのUI要素にあたるのかを記します。

実際のドキュメントの一部

これで以上です。

ドキュメントを書いてみて

ドキュメントを書くことでデザインシステムを構築する中での頭の整理にも繋がりますし、実際に活用される場合にFigmaやコードを観察したり対照させたりする必要がほとんどなく理解することができるため、開発のスピードも向上するのではないかと実感しました。

一方で、デザインシステムの構築自体、規模が大きければ大きくなるほど時間と労力が必要となるので、それぞれの事情やプロダクトの特性を見極めながら行う必要がありそうです。

さいごに

デザインシステム構築についての書籍や記事がまだまだ少ない中で、特にドキュメンテーションに関する情報はより少ないと言っても過言ではありません。 そんな中で、デザインシステムに関わる方の一助となればと思い、この記事を執筆してみました。

エキサイトホールディングス Advent Calendar 2023では、他にも弊社のエンジニアやデザイナーが執筆した記事が多く配信されていますので、ぜひチェックしてみてください。

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

Javaで整数型やbyte配列と16進表記の文字列との間の変換を行なう

こんにちは、エキサイト株式会社の平石です。エキサイトホールディングス Advent Calendar 2023の19日目を担当いたします。

今回は、Javaで整数型やbyte配列と16進表記の文字列との間の変換を行なう方法をご紹介します。

整数 → 16進表記 の変換

まずは、通常の10進表記(として表示される)整数を16進表記に変換してみます。

標準ライブラリのみを使う場合には、主に2つの方法があります。

IntegerやLongクラスのstaticメソッド

IntegerやLongクラスには、intやlongを16進表記に変換するstaticメソッドが用意されています。

var hexFromInt = Integer.toHexString(30);
var hexFromLong = Long.toHexString(255L);

System.out.println("30の16進表記: " + hexFromInt);
System.out.println("255Lの16進表記: " + hexFromLong);
30の16進表記: 1e
255Lの16進表記: ff

HexFormatクラスを使う

Java 17からはHexFormatクラスというものを利用することができます。

byte byteNum = 100;
short shortNum = 10000;
int intNum = 1000000;
long longNum = 100000000;

String hexFromByte = HexFormat.of().toHexDigits(byteNum);
String hexFromShort = HexFormat.of().toHexDigits(shortNum);
String hexFromInt = HexFormat.of().toHexDigits(intNum);
String hexFromLong = HexFormat.of().toHexDigits(longNum);

System.out.println("byteNumの16進表記: " + hexFromByte);
System.out.println("shortNumの16進表記: " + hexFromShort);
System.out.println("intNumの16進表記: " + hexFromInt);
System.out.println("longNumの16進表記: " + hexFromLong);
byteNumの16進表記: 64
shortNumの16進表記: 2710
intNumの16進表記: 000f4240
longNumの16進表記: 0000000005f5e100

このように、toHexDigitsに渡す引数の型によって最終的に出力される16進表記の長さも変わります。

Long型の整数は8バイトなので、1Lのような値を渡しても1ではなく0000000000000001が返ります。
一方、IntegerLongのstaticメソッドを利用するときには、1が返ります。
これが、この2つの方法の大きな違いですので、状況によって使い分けると良いでしょう。

HexFormatクラスについては後に詳しく紹介します。

16進表記 → 整数の変換

こちらも標準ライブラリのみを利用するのであれば、主に以下の2つの方法があります。

IntegerやLongクラスのstaticメソッド

IntegerやLongクラスのstaticメソッドを利用するには以下のようにします。

byte byteFromHex = Byte.parseByte("f", 16);
short shortFromHex = Short.parseShort("fff", 16);
int intFromHex = Integer.parseInt("fffff", 16);
long longFromHex = Long.parseLong("fffffff", 16);

System.out.println("f のパース結果: " + byteFromHex);
System.out.println("fff のパース結果: " + shortFromHex);
System.out.println("fffff のパース結果: " + intFromHex);
System.out.println("fffffff のパース結果: " + longFromHex);
f のパース結果: 15
fff のパース結果: 4095
fffff のパース結果: 1048575
fffffff のパース結果: 268435455

HexFormatクラスを使う

Java 17以降は、HexFormatクラスのstaticメソッドを利用しても、16進表記の文字列を整数に変換することができます。

int intFromHex = HexFormat.fromHexDigits("fffff");
long longFromHex = HexFormat.fromHexDigitsToLong("fffffff");
fffff のパース結果: 1048575
fffffff のパース結果: 268435455

HexFormatクラスによるbyte配列と16進表記の変換

HexFormatクラスを利用すれば、byte配列と16進表記の変換を行うことができます。

byte配列 → 16進表記

byte配列から16進表記の文字列に変換するには、formatHexメソッドを利用します。

byte[] bytes = {1, 15, 120, 127};
String byteHex = HexFormat.of().formatHex(bytes);

System.out.println(byteHex);
010f787f

ちなみに、負の数ではff(-1)から順に80(-128)となります。
(2の補数表現で-1は11111111なので当然といえば当然ですね。)

byte[] bytes = {-1, -2, -127, -128};
String byteHex = HexFormat.of().formatHex(bytes);
fffe8180

このメソッドを使って、ランダムな16進表記の文字列を作成できます。

final SecureRandom secureRandom = new SecureRandom();

final int numString = 64; // 生成したいランダムな文字列の文字数

final byte[] randomByte = new byte[numString / 2];
secureRandom.nextBytes(randomByte);

final String randomHex = HexFormat.of().formatHex(randomByte);

System.out.println(randomHex);

byte 1つにつき2文字に変換されるので、byte配列の長さはnumString / 2としています。

16進表記 → byte配列

逆に16進表記の文字列からbyte配列に変換するには、parseHexメソッドを利用します。
先ほどの例の変換後の文字列を、byte配列に戻してみましょう。

byte[] bytesFromHex = HexFormat.of().parseHex(byteHex); // byteHex = 010f787f

System.out.println(Arrays.toString(bytesFromHex));
[1, 15, 120, 127]

HexFormatクラスの詳細

最後に、HexFormatクラスの詳細についてみてみます。

これまでの例ではHexFormat.of()と書いてHexFormatインスタンスを取得していましたが、HexFormatインスタンスの取得のためにはofDelimiter()というstaticメソッドもあります。

of()delimiterなしのフォーマッター、ofDelimiter()delimitterありのフォーマッターを返します。

delimiterは、基本的には byte配列を16進表記に変換する際に区切り文字として使われます。 (toHexDigitsを使って単一のbyteや、int、longを変換する際には、delimiterは使われません。)

例えば、以下のように使います。

byte[] bytes = {1, 15, 120, 127};
String byteHex = HexFormat.ofDelimiter(":").formatHex(bytes);

System.out.println(byteHex);

byte[] bytesFromHex = HexFormat.ofDelimiter(":").parseHex(byteHex);

System.out.println(Arrays.toString(bytesFromHex));
01:0f:78:7f
[1, 15, 120, 127]

その他、HexFormatクラスのインスタンスに対しては、フォーマットの際の詳細設定を行うことができます。 prefix, suffix, delimiterはformatHexparseHexのみで利用されます。

  • withDelimiter・・・・delimiterを改めて指定できる
  • withLowerCase・・・16進表記に小文字(a ~ f)を使用するようになる
  • withUpperCase・・・16進表記に大文字(A ~ F)を使用するようになる
  • withPrefix・・・・・prefixを指定
  • withSuffix・・・・・suffixを指定
HexFormat hexFormat = HexFormat.of()
                .withDelimiter(", ")
                .withPrefix("#")
                .withSuffix(";")
                .withUpperCase();

byte[] bytes = {1, 15, 120, 127};

String hex = hexFormat.formatHex(bytes);
System.out.println(hex);

System.out.println(hexFormat.toHexDigits(255)); 
#01;, #0F;, #78;, #7F; // prefix, suffix, delimiterが適用される
000000FF // 単一の値の変換ではprefix, suffix, delimiterは適用されない

終わりに

今回は、Javaで整数型やbyte配列と16進表記の文字列との間の変換方法をご紹介しました。

では、また次回。

参考文献

AWSと外部システムを「良い感じ」に連携する

こちらはエキサイトホールディングス Advent Calendar 2023の18日目の記事になります。

qiita.com

まず最初に、

「良い感じ」とは

この記事では、アプリケーション側の実装を最小限に抑えることを前提に話を進めます。

概要

AWSと外部システム(SaaSなど)間でのデータ連携方法ですが、AWSにはAppFlowやEventBridgeなどのサービスを利用する方法があります。
ユースケースに応じて適切な選択をするため、いくつかの事例を元に解説してみます。

ユーザの処理をトリガーにして非同期にデータを連携したい

EventBridgeはイベント駆動型アーキテクチャの実装をサポートするサービスです。
AWSのHealthイベントが発行されたらSlackに通知する、みたいなことをやっている方も多いと思われます。

そういったAWSのサービスが生成するイベントに加え、ユーザが独自に定義したカスタムイベントの発行も可能となっています。 実装は超シンプルで、PutEvents APIをコールするだけです。PHPのコード例は以下の通り。

$result = $client->putEvents([
    'Entries' => [
        [
            'EventBusName' => 'arn:aws:events:ap-northeast-1:123456789012:event-bus/hoge',
            'Detail' => json_encode($detail),
            'DetailType' => 'Excite Hoge Event',
            'Source' => 'excite.hoge'
        ]
    ]
]);

たったこれだけです(もちろんエラーハンドリングなどは別途必要ですが)
これにより、アプリケーションはイベント発行のみを行うだけで済み、後続処理については考慮する必要がなくなり、処理が疎結合になることでアプリケーションコードの保守性を高めることができます。

決まったタイミングに大量のデータを送受信したい

SaaSAWS間でデータを安全にやり取りするには、フルマネージド統合サービスであるAppFlowが適切です。

AppFlowを使ったデータ連携の例として、SalesforceからRedshiftへの問合せデータの取り込みや、ZendeskからS3へのチケットデータの取り込み、といった事例がよく上げられます。
2023年12月現在、80近くのアプリケーションがサポートされており、日本でも広く採用されているアプリケーションもあります。

AppFlowの良さは、接続設定の容易さにあると思っています。とにかく簡単です。
当然、OAuth 2.0など認証フローもサポートされています。
データが散らばっていて分析が困難になっている、データレイク(の手前となる基盤)を構築したい、などの要望にもサクッと対応できるかと思います。

外部サービスからイベントを受け取って処理を実行したい

  • Salesforce
  • Zendesk
  • Shopify
  • Datadog
  • New Relic
  • Auth0
  • KARTE
  • Mackerel

などのSaaSパートナーからイベントを受け取れます。
例えば、Auth0でのログインイベントを受取り、LambdaをキックしてRDSにログイン履歴として保存する、などといった実装も可能となります。

Salesforceの例ではイベントリレーの設定が必要になることもありますが、(個人的な意見として)これは少し設定が煩雑です。
もう一つの方法として、AppFlowでデータを受け取り、EventBridgeにペイロードすることもできます。
AWS側の設定でほぼ完結できるので、IaCで統一管理できるといったメリットもあります。

https://aws.amazon.com/jp/blogs/compute/building-salesforce-integrations-with-amazon-eventbridge/

まとめ

定期的に「相手」を確認する処理では、リアルタイム性が損なわれがちです。
しかし、すべての処理を同期的に行うとなるとアプリケーションコードが肥大化し、保守性が低下します。

イベント駆動型アーキテクチャは非同期処理を実装する選択肢の1つとなりますので、何かしらの参考になれば幸いです。

複数回答チェックボックスで、1つ以上のチェックを必須にする方法

こんにちは。 エキサイト株式会社の三浦です。

こちらは、エキサイトホールディングス Advent Calendar 2023の18日目の記事になります。

qiita.com

良ければ他の記事もどうぞ!

さて、HTMLでフォームを作る際、「複数回答が用意されているチェックボックス式の設問に対して、最低1つ以上のチェックをしてもらいたい」という状況はよくあるでしょう。

今回はそういった場合に、コードにより1つ以上のチェックを必須にする方法を紹介します。

複数選択式チェックボックス

HTMLでフォームを作る場合、以下のような「複数回答が用意されているチェックボックス式の設問」を作ることはよくあるでしょう。

コードだと、以下のようになります。

なお簡単にするため、デザイン用のクラスやCSSは省略します。

<form method="POST" action="***">
    <div>どんな動物が好きですか?</div>

    <input id="animal-selection-dog"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-dog"
           value="犬"
    />
    <label for="animal-selection-dog"></label>

    <input id="animal-selection-cat"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-cat"
           value="猫"
    />
    <label for="animal-selection-cat"></label>

    <input id="animal-selection-crow"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-crow"
           value="カラス"
    />
    <label for="animal-selection-crow">カラス</label>

    <button type="submit">回答する</button>
</form>

これで設問の作成はできましたが、現状だと1つもチェックを入れていなくても問題なく回答できてしまいます。

ですが、実際は「1つ以上は必ずチェックして欲しい」という要件もよくあることでしょう。

では、「1つ以上のチェックを必須とする」にはどうすれば良いでしょうか。

1つ以上のチェックを必須にする方法:考えがちなミス

inputタグには、 required という属性があります。

それを使って以下のようにすれば良いのでは、と思う方も多いのではないでしょうか。

<!-- すべてのinputタグに required を追加 -->

<form method="POST" action="***">
    <div>どんな動物が好きですか?</div>

    <input id="animal-selection-dog"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-dog"
           value="犬"
           required
    />
    <label for="animal-selection-dog"></label>

    <input id="animal-selection-cat"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-cat"
           value="猫"
           required
    />
    <label for="animal-selection-cat"></label>

    <input id="animal-selection-crow"
            class="animal-selection"
            type="checkbox"
            name="animal-selection-crow"
            value="カラス"
            required
    />
    <label for="animal-selection-crow">カラス</label>

    <button type="submit">回答する</button>
</form>

では実際にこれで回答をしてみましょう。

なんと、すでに1つ以上の回答にチェックを入れているのにもかかわらず「チェックボックスをオンにしてください」と出てしまいました。

実は、チェックボックスは1つ1つのinputに対して required を判定するものであり、「1つ以上がチェックされているか」という判定は自動ではしてくれないのです。

では、どうすれば良いのでしょうか。

1つ以上のチェックを必須にする方法:正しい方法

残念ながら「1つ以上がチェックされているか」をHTMLだけで判定する方法は現状ありません。

JavaScriptを使うと良いでしょう。

<form method="POST" action="***">
    <div>どんな動物が好きですか?</div>

    <input id="animal-selection-dog"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-dog"
           value="犬"
           required
    />
    <label for="animal-selection-dog"></label>

    <input id="animal-selection-cat"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-cat"
           value="猫"
           required
    />
    <label for="animal-selection-cat"></label>

    <input id="animal-selection-crow"
           class="animal-selection"
           type="checkbox"
           name="animal-selection-crow"
           value="カラス"
           required
    />
    <label for="animal-selection-crow">カラス</label>

    <button type="submit">回答する</button>
</form>

<!-- 判定のため、JavaScriptを追加 -->
<script>
    (() => {
        // チェックボックスのinputタグを取得
        const checkBoxElements = Array.from(document.getElementsByClassName("animal-selection"));

        const errorMessage = "1つ以上の選択肢を選択してください。";
        checkBoxElements
            .forEach(m => {
                // エラーメッセージを、カスタムなものに変更
                m.setCustomValidity(errorMessage);

                // 各チェックボックスのチェックのオン・オフ時に、以下の処理が実行されるようにする
                m.addEventListener("change", () => {
                    // 1つ以上チェックがされているかどうかを判定
                    const isCheckedAtLeastOne = document.querySelector(".animal-selection:checked") !== null;

                    // 1つもチェックがされていなかったら、すべてのチェックボックスを required にする
                    // 加えて、エラーメッセージも変更する
                    checkBoxElements.forEach(n => {
                        n.required = !isCheckedAtLeastOne
                        n.setCustomValidity(isCheckedAtLeastOne ? "" : errorMessage);
                    });
                });
            });
    })();
</script>

少々コードが複雑に見えるかもしれませんが、やっていることは単純です。

  • 1つでもチェックが付いていたら、チェックボックスのinputタグ全てからrequired属性を排除
  • 1つもチェックが付いていなかったら、チェックボックスのinputタグ全てにrequired属性を追加
  • 加えて、エラーメッセージをカスタムなものに変更

これだけです。

実際にやってみると、以下のようになります。

もちろん、1つ以上チェックすれば問題なく回答することができます。

最後に

HTMLだけで複数回答チェックボックスの必須化をすることができないのは少々面倒ですが、それでも少しJavaScriptを加えれば対応可能です。

フォームを作成する上で必要になってくる場面も少なくないと思いますので、参考にしてもらえると幸いです!

accent-colorでチェックボックスの色を指定する方法と注意したい落とし穴(CSS)

こんにちは。 エキサイトで内定者アルバイトとしてデザイナーをしている齋藤です。

エキサイトホールディングス Advent Calendar 2023シリーズ2の12日目は、私齋藤が担当させていただきます。

今回はチェックボックスなどの強調表示色を指定できるCSSプロパティのaccent-colorの使用方法とその注意点についてご紹介したいと思います。

accent-colorとは

accent-colorCSSプロパティの一種で、以下の要素の強調表示色を指定することができます。

  • input要素(Type: checkboxradiorange
  • progress要素

以前はこれらの要素の色を指定したい場合、疑似要素を使用して無理やり表現していましたが、accent-colorの登場で一発で解決することができるようになりました。

対応ブラウザ

2023年12月18日時点の各ブラウザの対応状況は以下の通りです。

ブラウザ バージョン
Google Chrome 93以降
Microsoft Edge 93以降
Safari 15.4以降
Firefox 92以降
Opera 79以降
iOSSafari 15.4以降
AndroidGoogle Chrome 93以降
AndroidFirefox 92以降
AndroidOpera 79以降
Android WebView 93以降

なお、対応状況は変化する可能性があります。使用前にCan I use...などで確認をしてください。

実際に使ってみる

例はそれぞれ、<input type="radio" /> <input type="checkbox" /> <input type="range" />です。

通常時

通常はユーザーエージェントが指定した色(Chromeなら青色)が当てられます。

通常時の描写

この際、accent-colorは初期値のautoになっています。

accent-color適用時

accent-colorredを指定した場合、ユーザーエージェントが指定した色から任意の色に変更することができます。

accent-colorにredを指定した場合の描写

input {
    accent-color: red;
}

注意したい落とし穴

accent-colorは非常に便利なのですが、ひとつだけ注意したいことがあります。

例えば、エメラルドグリーン(#6ee7b7)を指定したとします。すると、以下のようにブラウザでは描写されます。

accent-colorに#6ee7b7を指定した場合の描写

あれ・・・?なにかがおかしい・・・

実は、accent-colorを指定して強調表示色を指定した場合、一定のコントラスト比を担保していない色ですと、ブラウザ側がaccent-colorを適用した要素にのみ自動的にダークモードにするため、指定する色のコントラスト比には十分注意する必要があります。

「一定のコントラスト比」の境界はブラウザのアルゴリズムによって異なるようですが、https://accent-color.glitch.me/で挙動を確認することができます。

先程指定した#6ee7b7は白に対して1:1.5コントラスト比でしたが、1:3.8#059666にすると通常のライトモードに戻りました。

accent-colorに#059666を指定した場合の描写

ブラウザによってラインが異なりますが、白に対して1:3以上の色を指定することをおすすめします。

※実験で使用したブラウザはGoogle Chrome(120.0.6099.109)です

さいごに

今回はaccent-colorを用いてチェックボックスの色を変える方法と、注意したい落とし穴についてご紹介しました。

エキサイトホールディングス Advent Calendar 2023では他にもエンジニアやデザイナーによる記事が多数投稿されています。ぜひチェックしてくださいね!

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

参考文献

Spring Bootでのキャッシュのアノテーションの使い方

はじめに

こんにちは、新卒1年目の岡崎です。エキサイトホールディングス Advent Calendar 2023の14日目を担当します。

今回は、Spring Bootで使うことができるキャッシュのアノテーションの機能を紹介していきたいと思います。

環境

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.6)
## Java

openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode)
------------------------------------------------------------
Gradle 7.4.1
------------------------------------------------------------

Build time:   2022-03-09 15:04:47 UTC
Revision:     36dc52588e09b4b72f2010bc07599e0ee0434e2e

Kotlin:       1.5.31
Groovy:       3.0.9
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.2 (Eclipse Adoptium 17.0.2+8)
OS:           Mac OS X 12.3 aarch64

設定

キャッシュを使うために、以下の設定を行う必要があります。

  • build.gradleの設定
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
}
  • application.ymlの設定
spring:
  redis:
    host: localhost
    port: 6379
  • キャッシュをする時に、返り値として使用するモデルにSerializableを継承させる
@Data
@Accessors(chain = true)
public class SampleModel implements Serializable {
    // 要素を記述    
}

どのようにSpring Bootでキャッシュのアノテーションをつけるのか

Spring Bootでは、アノテーションをつけることで、キャッシュをすることができます。
今回は、Spring Bootで用意されているキャッシュに関するアノテーションを三つ紹介します。

@Cacheable

@Cacheableをメソッドにつけることで、返り値をキャッシュすることができます。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

デフォルトでは、キャッシュの名前とメソッドの引数をキーとして、返り値を保存しています。

@CachePut

@CachePutでは、キーが同じキャッシュの返り値を更新することができます。キーはキャッシュ名とメソッドの引数を一致させる必要がありますが、アノテーションで用いるkeyというアトリビュートに値を入れることで、メソッドの引数の代わりに使用することができます。

この時に新しく更新される値は、@CachePutのアノテーションがついているメソッドの返り値になります。

@Cacheable(cacheNames = "GET_BOOK")
public Book getBook(Integer bookId) {
    // IDから本の情報を取得する
}

@CachePut(cacheNames = "GET_BOOK", key = "#book.getBookID()")
public Book updateBook(Book book) {
    // 本の情報を更新する
} 

@CacheEvict

@CacheEvictでは、キーが同じキャッシュを削除することができます。

@CacheEvict(cacheNames = "GET_BOOK", key = "#book.getBookID()")
public void updateBook(Book book) {
    // 本の情報を更新する
} 

オプション

@Cacheable、@CachePut、@CacheEvictでは、それぞれオプションが用意されています。ここでは、オプションを三つ紹介します。

key

keyというアトリビュートに任意の変数を入れることで、キャッシュのキーに使うメソッドの引数を自由に設定することができます。
以下は、キャッシュをする時に、メソッドの引数の中でもtypeとsizeだけ使用したい場合の一例の実装です。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST", key = "{#type, #size}")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

condition

conditionというアトリビュートに条件式を書くことで、特定の値であれば、キャッシュをするように動作することが可能です。
以下は、useCacheがtrueの時だけキャッシュをしたい場合の実装例です。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST", key = "{#type, #size}", condition = "#useCache")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

unless

unlessというアトリビュートに条件式を書くことで、特定の値であれば、キャッシュの動作をしないようにすることが可能です。
以下は、返ってきた値が空だった場合、キャッシュしないようにする場合の実装例です。

@Cacheable(cacheNames = "GET_BOOK_ID_LIST", key = "{#type, #size}", unless = "#result.isEmpty()")
public List<String> getBookIdList(String type, Integer size, Boolean useCache) {
    // 処理の記述を行う
}

補足: キャッシュはいつつけるのか?

初心者の場合、キャッシュをつければ良いのか、それともつけなくて良いのか、よく分からない場合があります。(少なくとも私はそうでした。)キャッシュの実装の有無を検討する時の一例を、補足として残しておきます。

結論から言うと、キャッシュはヒット率が高い時に有効です。

例えば、1分間に10人のユーザーが「ジャンルごとの本の一覧」を見ていて、その中でもAジャンルを7人が見ているとします。 この場合、同じクエリーパラメーターのアクセスが7割も来ているので、ヒット率は高いと言えそうです。

しかし、1分間に20人のユーザーが「検索」機能を使っていても、この中で誰一人として同じキーワードを使っていなかった場合、ヒット率は低いでしょう。

このように、アクセスが多くても、ユーザーが同じキーワードを使用しないような検索の場合は、キャッシュをするように実装を行っても、DBのアクセス量がほとんど変わらない可能性があります。 このような場合、キャッシュをする必要がないかもしれません。

同じクエリーパラメーターを持つアクセスがどれくらいあるかを検討し、その結果に基づいてキャッシュをするように実装を行うかどうか決めるといいかもしれません。

最後に

今回は、Spring Bootで使うことができるキャッシュのアノテーションの使い方を紹介しました。改めてキャッシュを適切に実装することが大切だと感じました。ここまで読んでいただきありがとうございました。

【Android】内部アプリ共有と、URLからインストールするための設定方法について

はじめに

エキサイト株式会社で内定者アルバイトをしている岡島です。 エキサイトホールディングス 2023 Advent Calendar シリーズ2の15日目を担当させていただきます。

今回はアプリ開発時に内部アプリ共有を試したので、 内部アプリ共有と詰まったことについて共有したいと思います。

内部アプリ共有とは

内部アプリ共有とはAndroidアプリ開発者向けの機能のことで、リンクを使ってAndroid App BundleやAPKをチームに共有できます。 アプリをテストするときなどに、素早く任意のユーザーが利用できるようになります。

  • 権限がある場合のみ、App Bundleや APK を内部共有用としてアップロードが可能
  • バージョンコードの再利用が可能
  • リンクを使ってアプリをダウンロードできるユーザーは100 人まで
  • リンクの有効期限はアップロード日の60日後

詳しくはドキュメントをご覧ください。

support.google.com

内部アプリ共有を有効に設定する

内部アプリ共有を使用してアプリをダウンロードする際には、設定で内部アプリ共有を有効にする必要があります。 私はこの部分で詰まったので、ドキュメントの補足説明をしようと思います。

(1) Google Playストアを開く。

(2) [アイコン] > [設定] に進む。

(3) [概要(About)] > [Playストアのバージョン] を7回タップ

(4) 設定に戻り、[一般(General)] > [開発者オプション] > [内部アプリ共有] をオンにする

以上で内部アプリ共有有効化の設定が完了です!

私は[Playストアのバージョン] を7回タップといった操作や[内部アプリ共有]ボタンの場所がわからず戸惑ってしまいました。

これらの操作を行うことでAndroid端末で内部アプリ共有が可能になります。

最後に

この記事では、Androidアプリ開発者向けの機能である「内部アプリ共有」についてご紹介しました。 実際に試してみる中で、設定の一部で少し戸惑うことがありましたが、アプリのテストが簡単に行えて便利でした。

ご覧いただき、ありがとうございました。

参考文献

内部アプリ共有 | Google Play Console

Quick share / internal app sharing / 内部アプリ共有でaab、apkをURLで共有する #Android - Qiita

エンジニア母の復職前の不安と実際の経験

こんにちは。エキサイト株式会社のあはれんです。

エキサイトホールディングス2023アドベントカレンダー14日目をお届けします。

qiita.com

今年の4月に時短勤務で復帰して、1歳児の母をやりながらエンジニアとして働いています。

エンジニアの女性は周りに少なく、復職前は自分が働きながら育児ができるのか不安でした。 漠然とした不安に直面していましたが、実際に復職してみてどうだったのか、その経験を共有したいと思います。

仕事と育児の両立は、エンジニアという職種や性別に関係ないかもしれませんが、同じ悩むを持つ方々にとって、少しでも参考になれば幸いです。

Q. 仕事と育児を両立しながら生活できるのか?

A: できてます

子供が生まれる前は、通常時は10:00-18:00で勤務し、繁忙期には21時頃まで働くこともありました。 仕事で体力や集中力を使い切って、退勤後は仕事の疲れを癒すことで精一杯だったので、これに育児というタスクが追加されて、体力が持つのか、生活ができるのか不安でした。

そんな不安もあり、私は時短勤務で復職し、9:00-16:00 で勤務しています。 またリモートワークをしているので、通勤時間が大幅に短縮できています。

退勤後は、保育園に子供を迎えに行き、その後は家事をこなしながら子供と遊んで過ごしています。 子供が寝た後に、明日の準備をしつつ、個人の時間として、勉強したり趣味を楽しんだりしています。 個人の時間は大幅に減りましたが、それでも無理なく日々を過ごすことができています。

育児と仕事を両立することができているのは、 時短勤務による仕事量の調整や、リモートワークを受け入れてくれているチームメンバーの協力のおかげです。 この協力に本当に感謝しています。

Q. 子供の体調不良で休みが続くことで仕事に支障はないのでしょうか?

A. チームメンバーの協力のおかげで、できてます

保育園から帰宅後は、手と足を念入りに洗い、夜はたっぷり寝かせ、出掛ける際は人混みを避けて…とできる限りの対策をしますが、 子供の風邪を防ぐことは難しいです。

1週間に2日しか出勤しないときもありましたが、 仕事の量やスケジュール調整していただき、乗り越えることができました。

本当にチームメンバーには感謝の気持ちでいっぱいです。

Q. 子供を見ながら仕事はできますか?

A. 基本は無理ですが、ミーティングならギリギリできる時があります

テレビを見てもらったり、一人で遊んでもらいますが、やはり親が居ると一緒に遊びたいので近寄ってきます。 モニターやキーボードを触ったり、遊びに誘ってくることもしばしばあり、それを断りながら仕事を進めるのは非常に難しい状況です。

ミーティングなどの人が話している状況だと、 子供も話に興味を持って静かに聞いてくれたりすることもあるので、 ギリギリ参加できることがあります。

ただし、基本的には難しい状況です。

Q. スキルアップのための時間は確保できます?

A. 業務中や夜に少しずつコツコツやっています

以前は、休日に数時間集中して開発したりすることがありましたが、そのような時間の確保は難しくなりました。 なので、業務中や夜に毎日少しずつコツコツと学習に励んでいます。

子供が生まれてから、子供に尊敬されると人間になりたいという気持ちが芽生え、以前よりスキルアップに対するモチベーションが高まった気がします。

1日10分でも時間を確保することで、先日、AWS DVAに合格することができました。 ガッツリ開発できるほどの時間はありませんが、毎日少しずつ継続することで、自身の技術力を向上させるように励んでいます。

まとめ

家庭の状況によって変わると思いますが、一例として紹介させていただきました。

弊社の制度(フレックスタイム/リモートワーク等)と、チームメンバーの手厚い協力のおかげで、育児しながらで働くことができています。本当に感謝しかありません..!!!

弊社ではエンジニア募集していますので、ぜひ興味ある方はチェックしてみてください。

www.wantedly.com

JavaのシリアライザであるKryoで圧縮設定を追加する

エキサイト株式会社メディア事業部エンジニアの佐々木です。以前Kryoを用いてJava内のデータをシリアライズする記事を書きました。

tech.excite.co.jp

その後、弊社内でもポツポツKryoを使っていますが、Kryo公式にシリアライズデータをさらに圧縮する方法があるのでご紹介します。

前提

前回のものと同じ条件にします。

$ java --version
openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment Temurin-17.0.2+8 (build 17.0.2+8)
OpenJDK 64-Bit Server VM Temurin-17.0.2+8 (build 17.0.2+8, mixed mode)

$ ./gradlew --version

------------------------------------------------------------
Gradle 7.6.1
------------------------------------------------------------

Build time:   2023-02-24 13:54:42 UTC
Revision:     3905fe8ac072bbd925c70ddbddddf4463341f4b4

Kotlin:       1.7.10
Groovy:       3.0.13
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.2 (Eclipse Adoptium 17.0.2+8)
OS:           Mac OS X 12.5 aarch64

前回のコード

前回のコードは下記になります。(多少差分が見やすいように変更を加えています。)

サンプルで用意したデータモデルをKryoを通してシリアライズとデシリアライズするというものになります。

public class KryoSample {

    public void main(){

        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        DataModel dataModel = new DataModel();
        byte[] bytes = serialize(kryo, outputStream, dataModel);

        DataModel deserializeDataModel = deserialize(kryo, bytes);
        System.out.println("Kryo Serialize and Deserialize consistency: " + dataModel.equals(deserializeDataModel));

    }

    private DataModel deserialize(Kryo kryo, byte[] bytes) {
        try (Input input = new Input(new ByteArrayInputStream(bytes))) {
            DataModel dataModel = kryo.readObject(input, DataModel.class);
            return dataModel;
        }
    }

    private byte[] serialize(Kryo kryo, ByteArrayOutputStream outputStream, DataModel dataModel) {
        try (Output output = new Output(outputStream)) {
            kryo.writeObject(output, dataModel);
            output.flush();
        }
        byte[] bytes = outputStream.toByteArray();
        return bytes;
    }

}

圧縮処理を追加したコード

圧縮処理を追加する場合は下記のようなコードになります。

public class KryoSample {

    public void main(){

        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        DataModel dataModel = new DataModel();
        byte[] bytes = serialize(kryo, outputStream, dataModel);

        DataModel deserializeDataModel = deserialize(kryo, bytes);
        System.out.println("Kryo deflate Serialize and Deserialize consistency: " + dataModel.equals(deserializeDataModel));


    }

    private DataModel deserialize(Kryo kryo, byte[] bytes) {
        try (InflaterInputStream inflaterInputStream = new InflaterInputStream(new ByteArrayInputStream(bytes));  // 圧縮データを解凍するためのInflaterInputStreamを通す
             Input input = new Input(inflaterInputStream)) {
            DataModel dataModel = kryo.readObject(input, DataModel.class);
            return dataModel;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private byte[] serialize(Kryo kryo, ByteArrayOutputStream outputStream, DataModel dataModel) {
        try (
                DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(outputStream);  // データを圧縮するためのDeflaterInputStreamを通す
                Output output = new Output(deflaterOutputStream)) {
            kryo.writeObject(output, dataModel);
            output.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        byte[] bytes = outputStream.toByteArray();
        return bytes;
    }

}

Kryoが用意してくれている圧縮・解凍用のInputStream/OutputStreamを通すだけになります。チェック例外が投げられるので、try-catchをする必要があります。

テスト実行

上記を実行します。

Kryo Serialize and Deserialize consistency: true
Kryo deflate Serialize and Deserialize consistency: true

上記の通り、圧縮・解凍処理の方も正常に実行されています。

サイズの比較

圧縮率等を比較するために約1000文字のテキストを使用して比較します。

original size:2974
deflate size:1207

約60%サイズダウンになっています。データにもよりますけど、ネットワーク転送量などがこのくらい下がるとお財布にも優しそうです。

まとめ

今回は、Kryoで簡単にデータ圧縮を行う方法をご紹介させていただきました。弊社でよく利用しているSpringBootですとJacksonでシリアライズすることが多いですが、JSONデータにして保存するのでサイズはKryoの通常とあまり変わらない印象です。Kryoだと少ない手間で圧縮・解凍ができ、CPU使用率もほとんど増えないので、弊社での導入も継続的に行おうと思います。

最後に

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

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