• TODO: スクリプト部分を信頼性のありそうなライブラリに換装
<div style="display: flex; justify-content: space-between; width:500px; margin: 100px auto;">
  <p>Simple&nbsp;<span style="color: red" data-tooltip_01-content="Message">[hover]</span></p>
  <p>HTML1&nbsp;<span style="color: red" data-tooltip_01-content="#content">[hover]</span></p>
  <p>HTML2&nbsp;<span style="color: red" data-tooltip_01-content="#content">[hover]</span></p>
</div>

<div id="content" class="tooltip_01--content">
  <p><b>Title</b></p>
  <ul>
      <li><a href="#">Contents</a></li>
      <li><a href="#">Contents</a></li>
      <li><a href="#">Contents</a></li>
  </ul>
</div>
/* No context defined. */
  • Content:
    // =====================================================================================
    //
    // tooltip_01
    //
    // =====================================================================================
    
    @use 'scss/global' as g;
    
    .tooltip_01--content {
      position: absolute;
      top: 0;
      left: 0;
      display: none;
      padding: 8px 16px;
      background: #fff;
      border: 1px solid #ddd;
      border-radius: 4px;
      box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15);
    
      &.is_ready {
        display: inline-block;
      }
    
      &-top {
        &::before {
          position: absolute;
          bottom: -16px;
          left: 50%;
          content: '';
          border: 8px solid transparent;
          border-top: 8px solid #ddd;
          transform: translateX(-50%);
        }
    
        &::after {
          position: absolute;
          bottom: -12px;
          left: 50%;
          content: '';
          border: 6px solid transparent;
          border-top: 6px solid #fff;
          transform: translateX(-50%);
        }
      }
    
      &-bottom {
        &::before {
          position: absolute;
          top: -16px;
          left: 50%;
          content: '';
          border: 8px solid transparent;
          border-bottom: 8px solid #ddd;
          transform: translateX(-50%);
        }
    
        &::after {
          position: absolute;
          top: -12px;
          left: 50%;
          content: '';
          border: 6px solid transparent;
          border-bottom: 6px solid #fff;
          transform: translateX(-50%);
        }
      }
    
      &-left {
        &::before {
          position: absolute;
          top: 50%;
          right: -8px;
          content: '';
          border-top: 8px solid transparent;
          border-bottom: 8px solid transparent;
          border-left: 8px solid #ddd;
          transform: translateY(-50%);
        }
    
        &::after {
          position: absolute;
          top: 50%;
          right: -6px;
          content: '';
          border-top: 6px solid transparent;
          border-bottom: 6px solid transparent;
          border-left: 6px solid #fff;
          transform: translateY(-50%);
        }
      }
    
      &-right {
        &::before {
          position: absolute;
          top: 50%;
          left: -8px;
          content: '';
          border-top: 8px solid transparent;
          border-right: 8px solid #ddd;
          border-bottom: 8px solid transparent;
          transform: translateY(-50%);
        }
    
        &::after {
          position: absolute;
          top: 50%;
          left: -6px;
          content: '';
          border-top: 6px solid transparent;
          border-right: 6px solid #fff;
          border-bottom: 6px solid transparent;
          transform: translateY(-50%);
        }
      }
    }
    
    @keyframes tooltip_01_top {
      from {
        opacity: 0;
        transform: translateY(5px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes tooltip_01_bottom {
      from {
        opacity: 0;
        transform: translateY(-5px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    
    @keyframes tooltip_01_left {
      from {
        opacity: 0;
        transform: translateX(5px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    @keyframes tooltip_01_right {
      from {
        opacity: 0;
        transform: translateX(-5px);
      }
      to {
        opacity: 1;
        transform: translateX(0);
      }
    }
    
    .tooltip_01-show_anim_top {
      animation: tooltip_01_top 0.2s ease-out;
    }
    
    .tooltip_01-hide_anim_top {
      animation: tooltip_01_top 0.1s ease-in reverse;
    }
    
    .tooltip_01-show_anim_bottom {
      animation: tooltip_01_bottom 0.2s ease-out;
    }
    
    .tooltip_01-hide_anim_bottom {
      animation: tooltip_01_bottom 0.1s ease-in reverse;
    }
    
    .tooltip_01-show_anim_left {
      animation: tooltip_01_left 0.2s ease-out;
    }
    
    .tooltip_01-hide_anim_left {
      animation: tooltip_01_left 0.1s ease-in reverse;
    }
    
    .tooltip_01-show_anim_right {
      animation: tooltip_01_right 0.2s ease-out;
    }
    
    .tooltip_01-hide_anim_right {
      animation: tooltip_01_right 0.1s ease-in reverse;
    }
    
  • URL: /components/raw/tooltip_01/_tooltip_01.scss
  • Filesystem Path: src/assets/_parts/components/tooltip/tooltip_01/_tooltip_01.scss
  • Size: 3.7 KB
  • Content:
    // =====================================================================================
    //
    // tooltip_01
    //  - このファイルを「src/js/components」に移動して使用してください
    //  - 同種のコンポーネントを併用する場合は、取り込むか、新しいRootsにするなどしてください
    //
    // TODO: スクリプトは信頼性のあるライブラリ(候補:Tippy.js)をベースにする
    //
    // =====================================================================================
    
    import $ from 'jquery';
    
    class Rect {
      constructor(top, left, width, height) {
        this.top = top;
        this.left = left;
        this.width = width;
        this.height = height;
        this.bottom = this.top + this.height;
        this.right = this.left + this.width;
      }
    
      inView() {
        const $window = $(window);
        const scrollTop = $window.scrollTop();
        const scrollBottom = scrollTop + $window.height();
        const scrollLeft = $window.scrollLeft();
        const scrollRight = scrollLeft + $window.width();
    
        if (scrollBottom > this.top + this.height && scrollTop < this.top) {
          if (scrollRight > this.left + this.width && scrollLeft < this.left) {
            return true;
          }
        }
    
        return false;
      }
    
      intersect(top, left) {
        return (
          top >= this.top &&
          top <= this.top + this.height &&
          left >= this.left &&
          left <= this.left + this.width
        );
      }
    }
    
    export class Tooltip {
      static DEBUG_MODE = false;
      static MARGIN = 10;
      static PADDING = 3;
    
      constructor(rootsName, suffixNum, targetElement) {
        console.log(rootsName);
        this.rootsName = rootsName;
        this.suffixNum = suffixNum;
        this.id = rootsName + '_' + this.suffixNum;
        this.target = targetElement;
        this.content = null;
        this.$body = $('body');
        this.$target = $(targetElement);
        this.$content = null;
    
        this.targetRect = null;
        this.contentRect = null;
        this.contentPosition = '';
    
        if (Tooltip.DEBUG_MODE) {
          this.$c = $('<div></div>');
          this.$t = $('<div></div>');
        }
    
        this.content = this.$target.attr('data-' + this.rootsName + '-content');
    
        if (this.content.substr(0, 1) === '#') {
          this.$content = $(this.content).clone();
        } else {
          this.$content = $(
            '<div class="' +
              this.rootsName +
              '--content">' +
              this.content +
              '</div>'
          );
        }
    
        this.$content.addClass('is_ready');
        this.$target.on('mouseenter.' + this.rootsName, () => {
          this.show();
        });
      }
    
      get element() {
        return this.target;
      }
    
      capitalize(word) {
        return word.charAt(0).toUpperCase() + word.slice(1);
      }
    
      setPositions() {
        const contentWidth = this.$content.outerWidth();
        const contentHeight = this.$content.outerHeight();
        const targetWidth = this.$target.outerWidth();
        const targetHeight = this.$target.outerHeight();
        const targetOffsetTop = this.$target.offset().top;
        const targetOffsetLeft = this.$target.offset().left;
    
        const contentRectTop = new Rect(
          targetOffsetTop - contentHeight - Tooltip.MARGIN,
          contentWidth > targetWidth
            ? targetOffsetLeft + targetWidth / 2 - contentWidth / 2
            : targetOffsetLeft,
          Math.max(contentWidth, targetWidth),
          contentHeight + Tooltip.MARGIN
        );
    
        const contentRectBottom = new Rect(
          targetOffsetTop + targetHeight,
          contentRectTop.left,
          contentRectTop.width,
          contentRectTop.height
        );
    
        const contentRectLeft = new Rect(
          contentHeight > targetHeight
            ? targetOffsetTop + targetHeight / 2 - contentHeight / 2
            : targetOffsetTop,
          targetOffsetLeft - contentWidth - Tooltip.MARGIN,
          contentRectTop.width + Tooltip.MARGIN,
          Math.max(contentHeight, targetHeight)
        );
    
        const contentRectRight = new Rect(
          contentRectLeft.top,
          targetOffsetLeft + targetWidth,
          contentRectLeft.width,
          contentRectLeft.height
        );
    
        if (contentRectTop.inView()) {
          this.contentPosition = 'top';
          this.contentRect = contentRectTop;
        } else if (contentRectBottom.inView()) {
          this.contentPosition = 'bottom';
          this.contentRect = contentRectBottom;
        } else if (contentRectRight.inView()) {
          this.contentPosition = 'right';
          this.contentRect = contentRectRight;
        } else if (contentRectLeft.inView()) {
          this.contentPosition = 'left';
          this.contentRect = contentRectLeft;
        } else {
          this.contentPosition = 'top';
          this.contentRect = contentRectTop;
        }
    
        this.targetRect = new Rect(
          targetOffsetTop - Tooltip.PADDING,
          targetOffsetLeft - Tooltip.PADDING,
          targetWidth + Tooltip.PADDING * 2,
          targetHeight + Tooltip.PADDING * 2
        );
    
        this.$content
          .css({
            top:
              this.contentPosition === 'bottom'
                ? this.contentRect.top + Tooltip.MARGIN
                : this.contentRect.top,
            left:
              this.contentPosition === 'right'
                ? this.contentRect.left + Tooltip.MARGIN
                : this.contentRect.left,
          })
          .addClass(this.rootsName + '--content-' + this.contentPosition);
    
        if (Tooltip.DEBUG_MODE) {
          this.$t
            .css({
              position: 'absolute',
              top: this.targetRect.top,
              left: this.targetRect.left,
              width: this.targetRect.width,
              height: this.targetRect.height,
              background: 'rgba(0,0,0,.4)',
              pointerEvents: 'none',
            })
            .appendTo(this.$body);
    
          this.$c
            .css({
              position: 'absolute',
              top: this.contentRect.top,
              left: this.contentRect.left,
              width: this.contentRect.width,
              height: this.contentRect.height,
              background: 'rgba(0,0,0,.4)',
              pointerEvents: 'none',
            })
            .appendTo(this.$body);
        }
      }
    
      show() {
        this.$content.appendTo(this.$body);
        this.$target.attr('aria-describedby', this.id).off('mouseenter.tooltip');
    
        this.setPositions();
    
        this.$content
          .on('animationend.' + this.rootsName, () => {
            this.$content
              .removeClass(this.rootsName + '-show_anim_' + this.contentPosition)
              .off('animationend.' + this.rootsName);
          })
          .attr('id', this.id)
          .addClass(this.rootsName + '-show_anim_' + this.contentPosition);
    
        this.$body.on('mousemove.' + this.id, (e) => {
          if (this.contentRect.intersect(e.pageY, e.pageX)) {
            return;
          }
          if (this.targetRect.intersect(e.pageY, e.pageX)) {
            return;
          }
    
          this.$body.off('mousemove.' + this.id);
          this.hide();
        });
      }
    
      hide() {
        this.$target.removeAttr('aria-describedby');
        this.$content
          .on('animationend.tooltip', () => {
            this.$content
              .removeClass(this.rootsName + '--content-' + this.contentPosition)
              .removeClass(this.rootsName + '-hide_anim_' + this.contentPosition)
              .removeClass('is_show')
              .attr('id', null)
              .off('animationend.tooltip')
              .remove();
          })
          .addClass(this.rootsName + '-hide_anim_' + this.contentPosition);
    
        this.$target.on('mouseenter.' + this.rootsName, (e) => {
          this.show();
        });
    
        if (Tooltip.DEBUG_MODE) {
          this.$t.remove();
          this.$c.remove();
        }
      }
    }
    
    export default function (rootsName) {
      $(() => {
        Tooltip.DEBUG_MODE = false;
        $('[data-' + rootsName + '-content]').each((index, element) => {
          new Tooltip(rootsName, index, element);
        });
      });
    }
    
  • URL: /components/raw/tooltip_01/tooltip_01.js
  • Filesystem Path: src/assets/_parts/components/tooltip/tooltip_01/tooltip_01.js
  • Size: 7.5 KB