lasagna_docs

isTouchDevice

タッチデバイスかどうかを判定します。

Source code

export const isTouchDevice =
  typeof window !== 'undefined' && window.ontouchstart !== null

cssVal

HTMLタグに設置されたCSSの値を取得します。

Parameters

Name Type Description

property String CSSプロパティ

Source code

export const cssVal = (property) => {
  return getComputedStyle(document.querySelector('html')).getPropertyValue(
    property
  )
}

$

DOM要素を返します。

Parameters

Name Type Description

selector String セレクタ

el HTMLElement 親要素

Example Usage

const element = $(".selector");

Source code

export const $ = (selector, el) => {
  if (!el) el = document;

  if (selector instanceof HTMLElement || selector instanceof SVGElement) {
    el = selector;
  } else {
    el = el.querySelector(selector);
  }
  if (el === null) return null;

  el.rect = (rectString) => {
    switch (rectString) {
      case 'x':
        return el.getBoundingClientRect().x;
      case 'y':
        return el.getBoundingClientRect().y;
      case 'left':
        return el.getBoundingClientRect().x;
      case 'top':
        return el.getBoundingClientRect().y;
      case 'width':
        return el.getBoundingClientRect().width;
      case 'height':
        return el.getBoundingClientRect().height;
      default:
        throw new Error(`Invalid property: ${rectString}`);
    }
  };

  return el;
};

$$

DOM要素を配列にして返します。

Parameters

Name Type Description

selector String セレクタ

el HTMLElement 親要素

Example Usage

const selector = $$(".selector");

Source code

export const $$ = (selector, el) => {
  if (!el) el = document
  return Array.from(el.querySelectorAll(selector)).map((el) => $(el))
}

randomId

ランダムなIDを生成します。

Source code

export const randomId = () => {
  const LENGTH = 4
  const SOURCE = 'abcdefghijklmnopqrstuvwxyz0123456789'
  let result = ''
  for (let i = 0; i < LENGTH; i++) {
    result += SOURCE[Math.floor(Math.random() * SOURCE.length)]
  }
  return result
}

spOnly

SPサイズの時のみtrueを返します。

Example Usage

if (spOnly) {
	console.log("SPサイズの時のみ実行される");
}

Source code

export const spOnly =
  typeof window !== 'undefined' &&
  window.matchMedia('(max-width: 751px)').matches

getDeviceType

デバイスタイプを取得します。

Example Usage

const deviceType = getDeviceType();
if (deviceType === "sp") {
	console.log("SPサイズの時のみ実行される");
} else if (deviceType === "tb") {
	console.log("タブレットサイズの時のみ実行される");
} else if (deviceType === "pc") {
	console.log("PCサイズの時のみ実行される");
}

Source code

export const getDeviceType = () => {
  if (typeof window !== 'undefined') {
    if (window.matchMedia('(max-width: 751px)').matches) {
      return 'sp'
    } else if (window.matchMedia('(max-width: 992px)').matches) {
      return 'tb'
    } else {
      return 'pc'
    }
  }
}

touchOnly

タッチデバイスであれば true を返します。

Source code

export const touchOnly = () => {
  const ua = navigator.userAgent.toLowerCase()
  return (
    /android|ipod|ipad|iphone|macintosh/.test(ua) && 'ontouchend' in document
  )
}

OneLineSplit

文字列を一行ごとに分割し、spanタグに格納します。

Parameters

Name Type Description

obj Object

obj.target String 分割対象の要素

obj.className String 分割された単語に設定するクラス

Example Usage

const split = new OneLineSplit({
	target: ".split-text",
	className: "split-text",
});
split.init(); // 分割
split.kill(); // 元に戻す

Source code

export class OneLineSplit {
  constructor(obj) {
    this.obj = obj
    this.init()

    const target = this.obj.target
    let element = null
    if (typeof target === 'object') {
      element = target
    } else if (typeof target === 'string') {
      element = document.querySelector(target)
    }

    this.lines = element.querySelectorAll('.split-text')
  }

  init() {
    const target = this.obj.target
    const className = this.obj.className || 'split-text'

    let element = null
    if (typeof target === 'object') {
      element = target
    } else if (typeof target === 'string') {
      element = document.querySelector(target)
    }

    if (element === null) return
    this.obj.element = element
    this.obj.old = element.innerHTML

    this.obj.element
      .querySelectorAll(
        `br[${window.innerWidth < 750 ? 'pc-only' : 'sp-only'}]`
      )
      .forEach((item) => {
        item.remove()
      })

    element.style.visibility = 'hidden'
    let spanWrapText = ''
    const nodes = [...element.childNodes]
    nodes.forEach((node) => {
      if (node.nodeType === 3) {
        const text = node.textContent.replace(/\r?\n/g, '\n')
        spanWrapText =
          spanWrapText +
          text.split('').reduce((acc, v) => {
            return acc + `<span>${v}</span>`
          }, '')
      } else if (node.nodeType === 1) {
        const tag = node.tagName.toLowerCase()
        if (tag !== 'br') {
          const attributes = node.attributes
          let attr = ''
          for (let i = 0; i < attributes.length; i++) {
            attr += `${attributes[i].name}="${attributes[i].value}" `
          }
          spanWrapText =
            spanWrapText +
            `<${tag} ${attr} char-ignore>${node.innerHTML}</${tag}>`
        } else {
          spanWrapText = spanWrapText + `<br>`
        }
      }
    })
    element.innerHTML = spanWrapText

    const spanEls = [...element.childNodes].filter((node) => {
      return node.nodeType === 1 && node.tagName.toLowerCase() !== 'br'
    })
    let tagList = []
    spanEls.forEach((el) => {
      const top = Math.round(el.getBoundingClientRect().top)
      el.setAttribute('char-pos', top)
      tagList.push(top)
    })

    tagList = tagList.filter(function (x, i, self) {
      return self.indexOf(x) === i
    })

    const textArray = []
    tagList.forEach((item) => {
      const classNameArray = document.querySelectorAll(`[char-pos="${item}"]`)
      let text = ''
      classNameArray.forEach((el) => {
        text += el.outerHTML
      })
      textArray.push(text)
    })
    let newSpanEl = ''
    textArray.forEach((text) => {
      if (text === '') return
      newSpanEl += `<span class="${className}" style="white-space: nowrap; display: inline-block; will-change: transform">${text}</span><br/>`
    })

    element.innerHTML = newSpanEl

    $$(`.${className}`, element).forEach((el) => {
      const array = [...$$('*', el)]
      const text = array
        .map((char) => {
          if (char.hasAttribute('char-ignore')) {
            char.removeAttribute('char-ignore')
            char.removeAttribute('char-pos')
            return char.outerHTML
          }
          return char.textContent
        })
        .join('')
      // el.innerHTML = `<span>${text}</span>`;
      el.innerHTML = text
    })
    element.style.visibility = ''
  }

  kill() {
    this.obj.element.innerHTML = this.obj.old
  }
}

WordsSplit

文章を単語ごとに分割します。

Parameters

Name Type Description

obj Object

obj.target String 分割対象の要素

obj.tag String 分割された単語に設定するタグ

Example Usage

const wordsSplit = new WordsSplit({
	target: document.getElementById("target"),
	tag: "span",
});
wordsSplit.init();
const words = wordsSplit.kill();

Source code

export class WordsSplit {
  constructor(obj){
    // example obj
    // {
    //   target: Element || String,
    //   tag: String,
    // }
    this.obj = obj
    this.splitWordsSetting = /(<[^>]+>)|[\p{sc=Han}]+|[\p{sc=Katakana}ー]+|[\p{sc=Hiragana}]+|[\p{P}]+|\b[\w'-]+\b|[\s]+/gu;

    this.rawText = this.obj.target.innerHTML
    this.obj.tag = this.obj.tag || "span"
  }

  init(){
    this.splitText = this.rawText.match(this.splitWordsSetting);
    
    const wordsSplitArray = this.splitText.map((text,index)=>{
      if(text.indexOf("<") === 0) return text
      if(text === " ") return "&nbsp;"
      return `<${this.obj.tag} style='display: inline-block'>${text}</${this.obj.tag}>`
    })
    this.obj.target.innerHTML = wordsSplitArray.join("");
    this.words = this.obj.target.querySelectorAll(this.obj.tag)
  }
  kill(){
    this.obj.target.innerHTML = this.rawText
  }
}

getPrime

素数を返します。

Parameters

Name Type Description

n Number 素数の個数

Source code

export const getPrime = (n) => {
  let count = 0
  let num = 1
  while (count < n) {
    num++
    let isPrime = true
    for (let i = 2; i < num; i++) {
      if (num % i === 0) {
        isPrime = false
        break
      }
    }
    if (isPrime) {
      count++
    }
  }
  return num
}

SwipeTracker

指定したHTML要素上でのタッチ操作を監視し、ユーザーが行うスワイプの方向(左右または上下)に応じたカスタムイベントを発火させるためのものです。

Parameters

Name Type Description

elem HTMLElement タッチ操作を監視するHTML要素

direction String スワイプの方向('lr'または'ud')

Example Usage

const elem = document.querySelector(".swipe");
SwipeTracker(elem, "lr");
elem.addEventListener("swipe.left", () => {
	console.log("swipe left");
});
elem.addEventListener("swipe.right", () => {
	console.log("swipe right");
});
elem.addEventListener("swipe.cancel", () => {
	console.log("swipe cancel");
});
elem.addEventListener("swipe.start", () => {
	console.log("swipe start");
});
elem.addEventListener("swipe.move", () => {
	console.log("swipe move");
});
elem.addEventListener("swipe.up", () => {
	console.log("swipe up");
});
elem.addEventListener("swipe.down", () => {
	console.log("swipe down");
});

Source code

export const SwipeTracker = (elem, direction = '') => {
  let x = 0
  let y = 0
  let target = null
  let startX = 0
  let startY = 0
  let moveX = 0
  let moveY = 0
  let thresholdX
  let thresholdY
  let eventList = {}

  // 指定されたエレメントを検出対象にする
  target = elem

  // ----- 諸々関数

  // スワイプと見なす閾値の設定、数値は用途で調整
  const setThreshold = function () {
    // 画面幅1/4または300の小さい方、割としっかり動かさないとスワイプにならない
    thresholdX = Math.min(window.innerWidth / 4, 300)
    // 画面高さ1/6または50の小さい方、縦は敏感
    thresholdY = Math.min(window.innerHeight / 6, 50)
  }

  // イベント発行。同じ名前のイベントを繰り返し作るのが気にならないなら不要
  const kickEvent = function (eventName) {
    if (eventList[eventName] == undefined) {
      eventList[eventName] = new Event(eventName)
    }
    target.dispatchEvent(eventList[eventName])
  }

  // 検出のリセット
  const resetSwipe = function () {
    x = 0
    y = 0
    startX = 0
    startY = 0
    moveX = 0
    moveY = 0
  }

  // スワイプのキャンセル
  const cencelSwipe = function () {
    resetSwipe()
    kickEvent('swipe.cancel')
  }

  // // 外部に移動量を教えるメソッド
  // this.posx = function () {
  //   return x
  // }
  // this.posy = function () {
  //   return y
  // }

  // ---- タッチイベントを監視

  // 開始
  target.addEventListener(
    'touchstart',
    (ev) => {
      // 開始座標記録
      startX = ev.touches[0].pageX
      startY = ev.touches[0].pageY
      moveX = startX
      moveY = startY
      kickEvent('swipe.start')
    },
    { passive: true }
  )
  // キャンセル
  target.addEventListener('touchcancel', cencelSwipe, { passive: true })
  // 移動
  target.addEventListener(
    'touchmove',
    (ev) => {
      moveX = ev.touches[0].pageX
      moveY = ev.touches[0].pageY
      // タッチ開始時からの差分
      x = moveX - startX
      y = moveY - startY
      kickEvent('swipe.move')
    },
    { passive: true }
  )
  // 終了
  target.addEventListener(
    'touchend',
    (ev) => {
      // 横方向の移動が閾値を超えてる=>左右スワイプ
      if (Math.abs(x) >= thresholdX) {
        if (x < 0) {
          kickEvent('swipe.left')
        } else {
          kickEvent('swipe.right')
        }
        // 左右の検出をしたかったけど閾値超えず=>キャンセル
      } else if (direction === 'lr') {
        kickEvent('swipe.cancel')
      }
      // 縦方向の移動が閾値を超えてる=>上下スワイプ
      if (Math.abs(y) >= thresholdY) {
        if (y < 0) {
          kickEvent('swipe.up')
        } else {
          kickEvent('swipe.down')
        }
      }
      // 上下の検出をしたかったけど閾値超えず=>キャンセル
      else if (direction === 'ud') {
        kickEvent('swipe.cancel')
      }
      // 何にせよ値リセット
      resetSwipe()
    },
    { passive: true }
  )

  // 呼ばれた時に、閾値設定を実行
  setThreshold()
  // 画面リサイズ時にも再実行
  window.addEventListener('resize', setThreshold)
}

shuffleArray

配列をシャッフルします。

Parameters

Name Type Description

array Array 配列

Source code

export const shuffleArray = (array) => {
  const cloneArray = [...array]

  for (let i = cloneArray.length - 1; i >= 0; i--) {
    let rand = Math.floor(Math.random() * (i + 1))
    // 配列の要素の順番を入れ替える
    let tmpStorage = cloneArray[i]
    cloneArray[i] = cloneArray[rand]
    cloneArray[rand] = tmpStorage
  }

  return cloneArray
}

scrollSet

'stop' か 'start' の引数を渡すとスクロールを制御できます。

Parameters

Name Type Description

toggle String 'stop' or 'start'

BodyScrollLocks Boolean true or false

Example Usage

$(".btn").addEventListener("click", () => {
	if ($(".menu").hasAttribute("open")) {
		scrollSet("start");
	} else {
		scrollSet("stop");
	}
});

Source code

export const scrollSet = (toggle, BodyScrollLocks) => {
  if (toggle === 'stop') {
    if (BodyScrollLocks) {
      disableBodyScroll($('.base-grad'))
      return
    }
    if (window.lenis) window.lenis.stop()
    document.querySelector('body').style.overflow = 'hidden'
  }
  if (toggle === 'start') {
    if (BodyScrollLocks) {
      clearAllBodyScrollLocks()
      return
    }
    if (window.lenis) window.lenis.start()
    document.querySelector('body').style.overflow = ''
  }
}

hoverRemoval

ホバー時に「hover-el」という属性をbuttonタグとaタグに追加します。

Example Usage

class App {
	constructor() {
		hoverRemoval();
	}
}

Source code

export const hoverRemoval = () => {
  document.querySelectorAll("button,a").forEach((el) => {
    el.addEventListener("touchstart", () => {
      el.setAttribute("hover-el", "")
    })
    el.addEventListener("touchend", () => {
      el.removeAttribute("hover-el") 
    })
  })
}

loopCount

数値型の引数から配列を生成します。

Parameters

Name Type Description

count Number 抽出する個数

Source code

export const loopCount = (count) => {
  return [...Array(count).keys()]
}

getQueryParams

URLパラメーターからクエリーを取得します。

Source code

export const getQueryParams = () => {
  const params = new URLSearchParams(window.location.search);
  const queryParams = {};
  for (const [key, value] of params.entries()) {
    if (value.includes(',')) {
      queryParams[key] = value.split(',').map(Number); // 数字に変換する場合
    } else {
      queryParams[key] = value;
    }
  }
  return queryParams;
}

DebugGrid

デザインのベースとなるグリッドを表示します。

Example Usage

<>
	<script>
		import {DebugGrid} from '@walkal0ne/lasagna'; // new
		DebugGrid();とかしなくても呼び出せます。
	</script>
	<template lang="html">
		<debug-grid></debug-grid>
	</template>
</>;

Source code

const isBrowser = typeof window !== 'undefined' && typeof HTMLElement !== 'undefined';

let DebugGrid;

if (isBrowser) {
	DebugGrid = class DebugGrid extends HTMLElement {

DebugImage

デザインのベースとなる画像を表示します。

Example Usage

<>
	<script>
		import {DebugImage} from '@walkal0ne/lasagna'; // new
		DebugImage();とかしなくても呼び出せます。
	</script>
	<template lang="html">
		<debug-image pc-image="pc.jpg" sp-image="sp.jpg"></debug-image>
	</template>
</>;

Source code

let DebugImage;

if (isBrowser) {
  DebugImage = class DebugImage extends HTMLElement{
    // コンストラクタでシャドウDOMを作成
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
    }
  
    // 要素がDOMに接続されたときの処理
    connectedCallback() {
      this.render();
      this.initToggle();
      this.listenKey();
    }
  
    // 要素がDOMから切り離されたときはイベントリスナーを解除
    disconnectedCallback() {
      document.removeEventListener('keydown', this.handleKeydown);
    }
  
    // シャドウDOM内にHTMLとスタイルを構築する
    render() {
      const template = document.createElement('template');
      template.innerHTML = `
        <style>
          :host {
            pointer-events: none;
            position: absolute;
            top: 0;
            left: 0;
            margin: auto;
            opacity: 0.5;
            z-index: 9999;
            width: 100vw;
            height: fit-content;
            display: none;
          }
          :host([debug-show]) {
            display: block;
          }
          .debug-image{
            width: 100%;
            height: auto;
            source, img {
              width: 100%;
              height: auto;
            }
          }
          
        </style>
        <picture class="debug-image">
          <source media="(min-width: 751px)" srcset="${this.getAttribute('pc-image')}">
          <source media="(max-width: 750px)" srcset="${this.getAttribute('sp-image')}">
          <img src="${this.getAttribute('pc-image')}" alt="表示する画像">
        </picture>
      `;
      this.shadowRoot.appendChild(template.content.cloneNode(true));
      const gridContainer = this.shadowRoot.querySelector('.debug-image');		
      // this.addImageClickListener(gridContainer);
    }
  
    // セッションストレージから状態を読み込み、表示を切り替え
    initToggle() {
      const toggle = sessionStorage.getItem('debugImageToggle');
      if (toggle !== null) {
        if (toggle === 'true') {
          this.setAttribute('debug-show', '');
        } else {
          this.removeAttribute('debug-show');
        }
      }
    }
  
    // 「P」キーでグリッドの表示/非表示を切り替える(大文字のみ対応)
    listenKey() {
      this.handleKeydown = (e) => {
        if (e.key === 'P') {
          if (this.hasAttribute('debug-show')) {
            this.removeAttribute('debug-show');
          } else {
            this.setAttribute('debug-show', '');
          }
          sessionStorage.setItem('debugImageToggle', String(this.hasAttribute('debug-show')));
        }
      };
      document.addEventListener('keydown', this.handleKeydown, false);
    }
  }
  
  // すでに登録されていなければカスタム要素として定義
  if (!customElements.get('debug-image')) {
    customElements.define('debug-image', DebugImage);
  }
}else{
  DebugImage = class {};
}

export { DebugImage };

safeCall

関数を安全に呼び出します。

Parameters

Name Type Description

fn Function 呼び出す関数

Example Usage

import { safeCall } from "@walkal0ne/lasagna";
export default class App {
	init() {
		if ($("[page-name='index']") === null) return;
		safeCall(this.opening.bind(this));
		safeCall(this.indexDirection.bind(this));
		safeCall(this.aboutDirection.bind(this));
		safeCall(this.globalDirection.bind(this));
	}
}
// or
[
	this.opening,
	this.indexDirection,
	this.aboutDirection,
	this.globalDirection,
].forEach((fn) => safeCall(fn.bind(this)));

Source code

export function safeCall(fn) {
	try {
		fn();
	} catch (e) {
		const name = fn.name || 'anonymous';
		console.error(`${name} error:`, e);
	}
}