如何更改Tour.js的巡视布局

时间:2018-05-20 12:47:31

标签: javascript jquery intro.js

我刚开始使用Tour.js

我添加了一个这样的按钮:

<button id="start-tour" class="btn btn-primary btn-block btn-lg">Start Tour</button>

js设置在文档末尾:

<script src="dist/js/tour.js"></script>
<script>
// Instantiate the tour:
var PageTour = $.fn.PageTour();
</script>
<script>
// Script to add dynamic "cities" for testing.
$('#start-tour').on('click', PageTour.open);
</script>

基本上它可以工作并对我指定的按钮和框做出反应:

<div class="box box-primary" data-tour-index="1" data-tour-title="Card Type" data-tour-description="A card will usually be one of multiple similar type items on a page.">

但现在问题是我不知道如何更改游览元素的布局(例如更改背景颜色等)。

更新: CAPTURE

我刚刚在我的问题中添加了巡回布局的捕获。我想更改包含卡片类型标题和...的旅行盒的布局css。

例如,我想将 #fff 中写的卡类型标题更改为#000 。那就是它!

我认为它必须在此tour.js文件中:

    $.fn.PageTour = function (opts) {
  var
    _o = $.extend({
      prefix : 'tour',
      horizontalPadding : 20,
      verticalPadding : 5,
      generalPadding : 5,
      nextText : 'Next',
      prevText : 'Previous',
      exitText : 'Exit',
      nextClass : 'btn btn-primary',
      prevClass : 'btn btn-default',
      exitClass : 'btn btn-danger',
      defaultIndex : 9999,
      styleOverwrites : {}
    }, opts),
    programmatic = false,
    programmaticNext = function () {},
    programmaticPrev = function () {},
    dims = {},
    doms = [],
    elId = 0,
    resizeTimer,
    elements = {
      body : $('body'),
      style : $('<style></style>'),
      overlay : $('<div></div>', { id : _o.prefix + '_overlay' }),
      tour : $('<div></div>', { id : _o.prefix + '_tour' }),
      title : $('<div></div>', {
        class : _o.prefix + '_title ' + _o.prefix + '_box_transition'
      }),
      description : $('<div></div>', {
        class : _o.prefix + '_description ' + _o.prefix + '_box_transition'
      }),
      descriptionText : $('<span></span>'),
      controls : $('<div></div>', {
        class : _o.prefix + '_controls'
      }),
      targets : $('<div></div>', {
        id : _o.prefix + '_targets',
        class : _o.prefix + '_element_transition ' + _o.prefix + '_glow ' + _o.prefix + '_animation_delay2'
      }),
      nextBtn : $('<a></a>', {
        html : _o.nextText,
        class : _o.nextClass
      }),
      prevBtn : $('<a></a>', {
        html : _o.prevText,
        class : _o.prevClass
      }),
      exit : $('<a></a>', {
        html : _o.exitText,
        class : _o.exitClass
      }),
      shadow : $('<div></div>', { class : _o.prefix + '_shade' })
    };

  function __init() {
    var
      medTarg = $('<div></div>', {
        class : _o.prefix + '_target ' +
        _o.prefix + '_target_medium ' +
        _o.prefix + '_element_transition ' +
        _o.prefix + '_glow ' +
        _o.prefix + '_animation_delay1'
      }),
      smTarg = $('<div></div>', {
        class : _o.prefix + '_target ' +
        _o.prefix + '_target_small ' +
        _o.prefix + '_element_transition ' +
        _o.prefix + '_glow'
      });

    medTarg.append(smTarg);
    elements.targets.append(medTarg);

    elements.prevBtn.on('click', prev);
    elements.nextBtn.on('click', next);
    elements.exit.on('click', exit);

    elements.description
      .append(elements.descriptionText);

    elements.controls
      .append(
        elements.prevBtn,
        elements.nextBtn,
        elements.exit
      );

    elements.tour
      .css({ display : 'none', top : 0, left : 0 })
      .append(
        elements.overlay,
        elements.title,
        elements.description,
        elements.controls,
        elements.targets
      );

    elements.overlay
      .css({ position : 'absolute', top : 0, left : 0 })
      .append(elements.shadow);

    elements.body.prepend(elements.tour);

    $(window).resize(function () {
      clearTimeout(resizeTimer);
      if (elements.tour.is(':visible')) {
        resizeTimer = setTimeout(function () {
          run();
        }, 100);
      }
    });

    setupStyle();
    elements.body.append(elements.style);

    return {
      switchTo : switchTo,
      next : next,
      prev : prev,
      open : open,
      exit : exit
    };
  }

  function open() {
    discoverDoms();

    if (doms.length === 0) {
      console.log("No tour elements.");
    } else {
      elements.tour.show();
      run();
    }
  }

  function switchTo(selector, options) {
    if (! options) options = {};
    programmatic = true;
    updateTourTitle(options.title || '');
    updateTourDesc(options.description || '');

    if (options.next) {
      programmaticNext = options.next;
      elements.nextBtn.show();
    } else {
      elements.nextBtn.hide();
    }
    if (options.prev) {
      elements.prevBtn.show();
      programmaticPrev = options.prev;
    } else {
      elements.prevBtn.hide();
    }

    elements.tour.show();

    var dom = typeof selector === 'string' ? $(selector) : selector;

    doTourCalculations(dom);
    shadowElement();
    flowTargets();
    flowTour(dom)
  }

  function run() {
    programmatic = false;
    updateTourTitle(doms[ elId ].attr('data-' + _o.prefix + '-title') || '');
    updateTourDesc(doms[ elId ].attr('data-' + _o.prefix + '-description') || '');
    doTourCalculations();
    shadowElement();
    flowTargets();
    flowTour()
  }

  function next() {
    if (! programmatic) {
      elId = elId + 1 === doms.length ? 0 : elId + 1;
      run();
    } else if (typeof programmaticNext === 'function') {
      programmaticNext();
    }
  }

  function prev() {
    if (! programmatic) {
      elId = elId === 0 ? doms.length - 1 : elId - 1;
      run();
    } else if (typeof programmaticPrev === 'function') {
      programmaticPrev();
    }
  }

  function exit() {
    elements.tour.hide();
  }

  function shadowElement() {
    elements.shadow.css({
      height : dims[ 'elHeight' ] + (_o.generalPadding * 4),
      width : dims[ 'elWidth' ] + (_o.generalPadding * 4),
      left : dims[ 'elLeft' ] - (_o.generalPadding * 2),
      top : dims[ 'elTop' ] - (_o.generalPadding * 2),
      'box-shadow' : '0px 0px 4px 5px rgba(0,0,0,0.8) inset, 0px 0px 5px 3000px rgba(0,0,0,0.8)'
    });
  }

  function discoverDoms() {
    // Rediscover means we're starting over
    elId = 0;

    doms = [];
    $('[data-' + _o.prefix + '-title], [data-' + _o.prefix + '-description]')
      .each(function () {
        doms.push($(this));
      });

    doms.sort(function (d1, d2) {
      var
        d1i = parseInt(d1.attr('data-' + _o.prefix + '-index') || _o.defaultIndex),
        d2i = parseInt(d2.attr('data-' + _o.prefix + '-index') || _o.defaultIndex);

      return d1i - d2i;
    });
  }

  function flowTargets() {

    var sqSize = dims[ 'elHeight' ] > dims[ 'elWidth' ]
      ? dims[ 'elHeight' ]
      : dims[ 'elWidth' ];

    // The sqSize smallest target must be bigger than the element.
    sqSize *= (1.5 * 1.5);

    // If the size of the box is tiny, give it a minimum height/width.
    if (sqSize < 100) sqSize = 100;

    // Find the X/Y to place the target box
    var
      myX = dims[ 'elCenterX' ] - (sqSize / 2),
      myY = dims[ 'elCenterY' ] - (sqSize / 2);

    // Animate the targets to the correct size and coordinates.
    $('#' + _o.prefix + '_targets').css({
      width : sqSize,
      height : sqSize,
      left : myX - (_o.horizontalPadding / 2) + _o.generalPadding,
      top : myY - _o.verticalPadding / 2
    });

  }

  function flowTour(dom) {
    if (! dom)
      dom = doms[ elId ]

    elements.title.css({
      'left' : dims[ 'title_x' ],
      'top' : dims[ 'title_y' ]
    });

    elements.description.css({
      'left' : dims[ 'desc_x' ],
      'top' : dims[ 'desc_y' ]
    });

    elements.controls.css({
      'left' : dims[ 'desc_x' ],
      'top' : dims[ 'desc_y' ] + dims[ 'descHeight' ] - _o.verticalPadding
    });

    var
      offset = dom.offset().top - 120,
      title = dims[ 'title_y' ] - 10;

    $('html, body').animate({
      scrollTop : offset > title ? offset : title
    }, 200);
  }

  function updateTourTitle(title) {
    var div = elements.title; // Get the title object.

    if (title === '')
      div.hide();
    else
      div.show();

    // Update the title of the tour.
    div.html(title); // Set the title.
    div.width(textWidth.apply(div) * 2.5); // Set the width of the title for reflow calculation.
  }

  function updateTourDesc(text) {
    var div = elements.description; // Get the parent description object.

    if (text === '')
      div.hide();
    else
      div.show();

    // Update the description of the tour element.
    var desc = div.children('span').first(); // Get the description span object.

    desc.html(text); // Set the description.
  }

  /**
   * Calculates out all the X/Y locations for where to put the title and the description/controls
   * base on the coordinates and dimensions of the show element and the tour elements.
   */
  function doTourCalculations(element) {
    if (! element) {
      element = doms[ elId ];
    }

    // Get all the height/width and X/Y coordinates of all the elements involved with the tour
    // including the window viewport dimensions.
    var
      titleIs,
      titleHeight = parseInt(elements.title.outerHeight()),
      titleWidth = parseInt(elements.title.outerWidth()),
      descHeight = parseInt(elements.description.outerHeight()),
      descWidth = parseInt(elements.description.outerWidth());

    dims[ 'descHeight' ] = descHeight;
    dims[ 'winWidth' ] = parseInt($(window).outerWidth());
    dims[ 'winHeight' ] = parseInt($(window).outerHeight());
    dims[ 'elWidth' ] = parseInt(element.outerWidth());
    dims[ 'elHeight' ] = parseInt(element.outerHeight());
    dims[ 'elLeft' ] = element.offset().left;
    dims[ 'elTop' ] = element.offset().top;
    dims[ 'elCenterX' ] = dims[ 'elLeft' ] + (dims[ 'elWidth' ] / 2);
    dims[ 'elCenterY' ] = dims[ 'elTop' ] + (dims[ 'elHeight' ] / 2);
    dims[ 'topSpace' ] = dims[ 'elTop' ];
    dims[ 'rightSpace' ] = dims[ 'winWidth' ] - (dims[ 'elLeft' ] + dims[ 'elWidth' ]);
    dims[ 'botSpace' ] = dims[ 'winHeight' ] - (dims[ 'elTop' ] + dims[ 'elHeight' ]); // How much space below the element
    dims[ 'leftSpace' ] = dims[ 'elLeft' ] - (_o.horizontalPadding * 2);

    // Get the X/Y coordinates for the title
    if (dims[ 'leftSpace' ] > (titleWidth + (_o.horizontalPadding * 2))) { // Check the left side space
      dims[ 'title_x' ] = dims[ 'elLeft' ] - titleWidth - (_o.horizontalPadding * 2);
      dims[ 'title_y' ] = dims[ 'elTop' ];
      if (dims[ 'topSpace' ] > ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3)) {
        dims[ 'title_y' ] = dims[ 'elTop' ] - ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3);
      }
      titleIs = 'left';
    } else if (dims[ 'rightSpace' ] > (titleWidth + (_o.horizontalPadding * 2))) { // Check the right side space
      dims[ 'title_x' ] = dims[ 'elLeft' ] + dims[ 'elWidth' ] + _o.horizontalPadding;
      dims[ 'title_y' ] = dims[ 'elTop' ];
      if (dims[ 'topSpace' ] > ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3)) {
        dims[ 'title_y' ] = dims[ 'elTop' ] - ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3);
      }
      titleIs = 'right';
    } else if (dims[ 'topSpace' ] > (titleHeight + (_o.verticalPadding * 2))) { // check for space above the element
      dims[ 'title_y' ] = dims[ 'elTop' ] - titleHeight - (_o.verticalPadding * 4);
      dims[ 'title_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth;
      titleIs = 'top';
    } else { // Must go on the bottom.
      dims[ 'title_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth;
      dims[ 'title_y' ] = dims[ 'elTop' ] + dims[ 'elHeight' ] + _o.verticalPadding;
      titleIs = 'bottom';
    }

    // Sets the page scrollTo Y coordinate based on the location of the title.
    if (titleIs !== 'bottom') {
      if ($(window).height() < (dims[ 'elTop' ] + dims[ 'elHeight' ])) {
        dims[ 'scrollTo' ] = dims[ 'title_y' ] - _o.verticalPadding;
      } else {
        dims[ 'scrollTo' ] = 0;
      }
    } else {
      dims[ 'scrollTo' ] = dims[ 'elTop' ] - (_o.verticalPadding * 2);
    }

    // Get the X/Y coordinates for the description/controls
    if (dims[ 'leftSpace' ] >= (descWidth + (_o.horizontalPadding * 2))) { // Check the left side space
      dims[ 'desc_x' ] = dims[ 'elLeft' ] - descWidth - (_o.horizontalPadding * 2);
      dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'left')
          ? titleHeight + _o.verticalPadding
          : 0
      );
    } else if (dims[ 'rightSpace' ] >= (descWidth + (_o.horizontalPadding * 2))) { // Check the right side space
      dims[ 'desc_x' ] = dims[ 'elLeft' ] + dims[ 'elWidth' ] + _o.horizontalPadding;
      dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'right')
          ? titleHeight + _o.verticalPadding
          : 0
      );
    } else if (dims[ 'topSpace' ] >= (descHeight + (_o.verticalPadding * 4) + ((titleIs === 'top')
          ? titleHeight
          : 0)
      ) && titleIs === 'top') { // check for space above the element
      dims[ 'desc_y' ] = dims[ 'elTop' ] - _o.verticalPadding - descHeight - ((titleIs === 'top')
          ? titleHeight + _o.verticalPadding
          : 0
      );
      dims[ 'desc_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth;
      if (titleIs === 'top') {
        dims[ 'title_y' ] -= descHeight;
      }
    } else { // Must go on the bottom.
      dims[ 'desc_x' ] = (dims[ 'winWidth' ] / 2) - descWidth;
      dims[ 'desc_y' ] = dims[ 'elTop' ] + dims[ 'elHeight' ] + (_o.verticalPadding * 2) + ((titleIs === 'bottom')
          ? titleHeight + _o.verticalPadding
          : 0
      );
      if (titleIs === 'right') {
        dims[ 'desc_x' ] = dims[ 'title_x' ] - (descWidth * 0.25);
        dims[ 'desc_y' ] = dims[ 'desc_y' ] + _o.verticalPadding;
      }
    }
  }

  function setupStyle() {
    var styleSheet = '';
    var styles = {
      'overlay' : {
        selector : '#' + _o.prefix + '_tour, #' + _o.prefix + '_overlay',
        style : 'height: 100vh; width: 100%;'
      },
      'tour' : {
        selector : '#' + _o.prefix + '_tour',
        style : 'position: absolute;'
      },
      'title' : {
        selector : '.' + _o.prefix + '_title',
        style : 'top: 0; left: 0; text-shadow: 0 0 10px #aaa; color: white; font-size: 2em; position: absolute; font-weight: bold !important; width: 20rem;'
      },
      'description' : {
        selector : '.' + _o.prefix + '_description',
        style : 'top: 0; left: 0; box-shadow: 0 0 7px #666; border-radius: 7px; padding: 11px; color: white; font-size: 20px; position: absolute; width: 35rem; font-weight: normal !important;'
      },
      'controls' : {
        selector : '.' + _o.prefix + '_controls',
        style : 'top: 0; left: 0; margin: 8px 0; text-align: right; padding: 11px; color: white; font-size: 20px; position: absolute; z-index: 10000; transition: all 0.75s; transition-timing-function: ease-in-out;'
      },
      'control_buttons' : {
        selector : '.' + _o.prefix + '_controls .btn',
        style : 'margin-right: 10px; box-shadow: 0 0 3px white;'
      },
      'targets' : {
        selector : '#' + _o.prefix + '_targets',
        style : 'top: 0; left: 0; border-radius: 50%; border: 1px solid #333; position: absolute; opacity: 0.3;'
      },
      'target_large' : {
        selector : '.' + _o.prefix + '_target',
        style : 'border-radius: 50%; position: relative; height: 80%; width: 80%; top: 9%; padding: 0; margin: 0 auto;'
      },
      'target_small' : {
        selector : '.' + _o.prefix + '_target_small',
        style : 'border: 2px solid #aaa;'
      },
      'target_medium' : {
        selector : '.' + _o.prefix + '_target_medium',
        style : 'border: 2px solid #666;'
      },
      'transitions_main' : {
        selector : '.' + _o.prefix + '_element_transition',
        style : 'transition: all 0.75s; transition-timing-function: ease-in-out;'
      },
      'transition_box' : {
        selector : '.' + _o.prefix + '_box_transition',
        style : 'transition-property: top, left; transition-duration: 0.75s; transition-timing-function: ease-in-out;'
      },
      'transition_key_frame' : {
        selector : '@keyframes targetGlow',
        style : 'from { box-shadow: 0 0 1px rgba(64, 64, 64, 0.78); } to { box-shadow: 0 0 8px white; }'
      },
      'target_glow' : {
        selector : '.' + _o.prefix + '_glow',
        style : 'animation-duration: 1s; animation-name: targetGlow; animation-iteration-count: infinite; animation-direction: alternate;'
      },
      'target_delay_1' : {
        selector : '.' + _o.prefix + '_animation_delay1',
        style : 'animation-delay: 0.3s;'
      },
      'target_delay_2' : {
        selector : '.' + _o.prefix + '_animation_delay2',
        style : 'animation-delay: 0.6s;'
      },
      'spotlight' : {
        selector : '.' + _o.prefix + '_shade',
        style : 'overflow: hidden; transition: all 0.75s; transition-timing-function: ease-in-out; position: absolute; border-radius: 5px;'
      }
    };

    for (var style in styles) {
      if (! styles.hasOwnProperty(style)) return;

      styleSheet += styles[ style ].selector + ' { ';

      if (_o.styleOverwrites.hasOwnProperty(style)) {
        styleSheet += _o.styleOverwrites[ style ];
      } else {
        styleSheet += styles[ style ].style;
      }

      styleSheet += ' }\n';
    }

    elements.style.html(styleSheet);
  }

  function textWidth() {
    var org = $(this);
    var html = $('<span>' + org.html() + '</span>');
    html.css({
      'font-family' : org.css('font-family'),
      'font-size' : org.css('font-size'),
      'postion' : 'absolute',
      'width' : 'auto',
      'left' : '-9999px'
    });
    $('body').append(html);
    var width = html.outerWidth();
    html.remove();
    return width;
  }

  return __init();
};

2 个答案:

答案 0 :(得分:0)

更新后的评论我得到了你 - 你不知道如何定位所有的旅游卡来设计它们。

目前你动态地构建它们,其中一些有类。我建议只添加额外的通用类来定位al tour卡:

elements = {
  ...,    
  title : $('<div></div>', {
      class : _o.prefix + '_title ' + _o.prefix + '_box_transition tour-card-title'
  }),
  description : $('<div></div>', {
    class : _o.prefix + '_description ' + _o.prefix + '_box_transition tour-card-description'
  }),
...
}

这将继续添加它现在的类,但也是我们在css中新的易于目标的类:

.tour-card-title {}

这就是你之后的事吗?

答案 1 :(得分:0)

这就是你如何设置从tour.js包裹的内部标识#tour_tour生成的所有内容的样式,然后你可以选择所有的选择器

enter image description here

你可以选择它们并按照那样设置它们

#tour_targets {
  background-color: #ccc;
}

#tour_tour .tour_title {
  color: red;
}


#tour_tour .tour_description {
  background-color: blue;
  color: white;
}

&#13;
&#13;
$.fn.PageTour = function (opts) {
  var
    _o = $.extend({
      prefix : 'tour',
      horizontalPadding : 20,
      verticalPadding : 5,
      generalPadding : 5,
      nextText : 'Next',
      prevText : 'Previous',
      exitText : 'Exit',
      nextClass : 'btn btn-primary',
      prevClass : 'btn btn-default',
      exitClass : 'btn btn-danger',
      defaultIndex : 9999,
      styleOverwrites : {}
    }, opts),
    programmatic = false,
    programmaticNext = function () {},
    programmaticPrev = function () {},
    dims = {},
    doms = [],
    elId = 0,
    resizeTimer,
    elements = {
      body : $('body'),
      style : $('<style></style>'),
      overlay : $('<div></div>', { id : _o.prefix + '_overlay' }),
      tour : $('<div></div>', { id : _o.prefix + '_tour' }),
      title : $('<div></div>', {
        class : _o.prefix + '_title ' + _o.prefix + '_box_transition'
      }),
      description : $('<div></div>', {
        class : _o.prefix + '_description ' + _o.prefix + '_box_transition'
      }),
      descriptionText : $('<span></span>'),
      controls : $('<div></div>', {
        class : _o.prefix + '_controls'
      }),
      targets : $('<div></div>', {
        id : _o.prefix + '_targets',
        class : _o.prefix + '_element_transition ' + _o.prefix + '_glow ' + _o.prefix + '_animation_delay2'
      }),
      nextBtn : $('<a></a>', {
        html : _o.nextText,
        class : _o.nextClass
      }),
      prevBtn : $('<a></a>', {
        html : _o.prevText,
        class : _o.prevClass
      }),
      exit : $('<a></a>', {
        html : _o.exitText,
        class : _o.exitClass
      }),
      shadow : $('<div></div>', { class : _o.prefix + '_shade' })
    };

  function __init() {
    var
      medTarg = $('<div></div>', {
        class : _o.prefix + '_target ' +
        _o.prefix + '_target_medium ' +
        _o.prefix + '_element_transition ' +
        _o.prefix + '_glow ' +
        _o.prefix + '_animation_delay1'
      }),
      smTarg = $('<div></div>', {
        class : _o.prefix + '_target ' +
        _o.prefix + '_target_small ' +
        _o.prefix + '_element_transition ' +
        _o.prefix + '_glow'
      });

    medTarg.append(smTarg);
    elements.targets.append(medTarg);

    elements.prevBtn.on('click', prev);
    elements.nextBtn.on('click', next);
    elements.exit.on('click', exit);

    elements.description
      .append(elements.descriptionText);

    elements.controls
      .append(
        elements.prevBtn,
        elements.nextBtn,
        elements.exit
      );

    elements.tour
      .css({ display : 'none', top : 0, left : 0 })
      .append(
        elements.overlay,
        elements.title,
        elements.description,
        elements.controls,
        elements.targets
      );

    elements.overlay
      .css({ position : 'absolute', top : 0, left : 0 })
      .append(elements.shadow);

    elements.body.prepend(elements.tour);

    $(window).resize(function () {
      clearTimeout(resizeTimer);
      if (elements.tour.is(':visible')) {
        resizeTimer = setTimeout(function () {
          run();
        }, 100);
      }
    });

    setupStyle();
    elements.body.append(elements.style);

    return {
      switchTo : switchTo,
      next : next,
      prev : prev,
      open : open,
      exit : exit
    };
  }

  function open() {
    discoverDoms();

    if (doms.length === 0) {
      console.log("No tour elements.");
    } else {
      elements.tour.show();
      run();
    }
  }

  function switchTo(selector, options) {
    if (! options) options = {};
    programmatic = true;
    updateTourTitle(options.title || '');
    updateTourDesc(options.description || '');

    if (options.next) {
      programmaticNext = options.next;
      elements.nextBtn.show();
    } else {
      elements.nextBtn.hide();
    }
    if (options.prev) {
      elements.prevBtn.show();
      programmaticPrev = options.prev;
    } else {
      elements.prevBtn.hide();
    }

    elements.tour.show();

    var dom = typeof selector === 'string' ? $(selector) : selector;

    doTourCalculations(dom);
    shadowElement();
    flowTargets();
    flowTour(dom)
  }

  function run() {
    programmatic = false;
    updateTourTitle(doms[ elId ].attr('data-' + _o.prefix + '-title') || '');
    updateTourDesc(doms[ elId ].attr('data-' + _o.prefix + '-description') || '');
    doTourCalculations();
    shadowElement();
    flowTargets();
    flowTour()
  }

  function next() {
    if (! programmatic) {
      elId = elId + 1 === doms.length ? 0 : elId + 1;
      run();
    } else if (typeof programmaticNext === 'function') {
      programmaticNext();
    }
  }

  function prev() {
    if (! programmatic) {
      elId = elId === 0 ? doms.length - 1 : elId - 1;
      run();
    } else if (typeof programmaticPrev === 'function') {
      programmaticPrev();
    }
  }

  function exit() {
    elements.tour.hide();
  }

  function shadowElement() {
    elements.shadow.css({
      height : dims[ 'elHeight' ] + (_o.generalPadding * 4),
      width : dims[ 'elWidth' ] + (_o.generalPadding * 4),
      left : dims[ 'elLeft' ] - (_o.generalPadding * 2),
      top : dims[ 'elTop' ] - (_o.generalPadding * 2),
      'box-shadow' : '0px 0px 4px 5px rgba(0,0,0,0.8) inset, 0px 0px 5px 3000px rgba(0,0,0,0.8)'
    });
  }

  function discoverDoms() {
    // Rediscover means we're starting over
    elId = 0;

    doms = [];
    $('[data-' + _o.prefix + '-title], [data-' + _o.prefix + '-description]')
      .each(function () {
        doms.push($(this));
      });

    doms.sort(function (d1, d2) {
      var
        d1i = parseInt(d1.attr('data-' + _o.prefix + '-index') || _o.defaultIndex),
        d2i = parseInt(d2.attr('data-' + _o.prefix + '-index') || _o.defaultIndex);

      return d1i - d2i;
    });
  }

  function flowTargets() {

    var sqSize = dims[ 'elHeight' ] > dims[ 'elWidth' ]
      ? dims[ 'elHeight' ]
      : dims[ 'elWidth' ];

    // The sqSize smallest target must be bigger than the element.
    sqSize *= (1.5 * 1.5);

    // If the size of the box is tiny, give it a minimum height/width.
    if (sqSize < 100) sqSize = 100;

    // Find the X/Y to place the target box
    var
      myX = dims[ 'elCenterX' ] - (sqSize / 2),
      myY = dims[ 'elCenterY' ] - (sqSize / 2);

    // Animate the targets to the correct size and coordinates.
    $('#' + _o.prefix + '_targets').css({
      width : sqSize,
      height : sqSize,
      left : myX - (_o.horizontalPadding / 2) + _o.generalPadding,
      top : myY - _o.verticalPadding / 2
    });

  }

  function flowTour(dom) {
    if (! dom)
      dom = doms[ elId ]

    elements.title.css({
      'left' : dims[ 'title_x' ],
      'top' : dims[ 'title_y' ]
    });

    elements.description.css({
      'left' : dims[ 'desc_x' ],
      'top' : dims[ 'desc_y' ]
    });

    elements.controls.css({
      'left' : dims[ 'desc_x' ],
      'top' : dims[ 'desc_y' ] + dims[ 'descHeight' ] - _o.verticalPadding
    });

    var
      offset = dom.offset().top - 120,
      title = dims[ 'title_y' ] - 10;

    $('html, body').animate({
      scrollTop : offset > title ? offset : title
    }, 200);
  }

  function updateTourTitle(title) {
    var div = elements.title; // Get the title object.

    if (title === '')
      div.hide();
    else
      div.show();

    // Update the title of the tour.
    div.html(title); // Set the title.
    div.width(textWidth.apply(div) * 2.5); // Set the width of the title for reflow calculation.
  }

  function updateTourDesc(text) {
    var div = elements.description; // Get the parent description object.

    if (text === '')
      div.hide();
    else
      div.show();

    // Update the description of the tour element.
    var desc = div.children('span').first(); // Get the description span object.

    desc.html(text); // Set the description.
  }

  /**
   * Calculates out all the X/Y locations for where to put the title and the description/controls
   * base on the coordinates and dimensions of the show element and the tour elements.
   */
  function doTourCalculations(element) {
    if (! element) {
      element = doms[ elId ];
    }

    // Get all the height/width and X/Y coordinates of all the elements involved with the tour
    // including the window viewport dimensions.
    var
      titleIs,
      titleHeight = parseInt(elements.title.outerHeight()),
      titleWidth = parseInt(elements.title.outerWidth()),
      descHeight = parseInt(elements.description.outerHeight()),
      descWidth = parseInt(elements.description.outerWidth());

    dims[ 'descHeight' ] = descHeight;
    dims[ 'winWidth' ] = parseInt($(window).outerWidth());
    dims[ 'winHeight' ] = parseInt($(window).outerHeight());
    dims[ 'elWidth' ] = parseInt(element.outerWidth());
    dims[ 'elHeight' ] = parseInt(element.outerHeight());
    dims[ 'elLeft' ] = element.offset().left;
    dims[ 'elTop' ] = element.offset().top;
    dims[ 'elCenterX' ] = dims[ 'elLeft' ] + (dims[ 'elWidth' ] / 2);
    dims[ 'elCenterY' ] = dims[ 'elTop' ] + (dims[ 'elHeight' ] / 2);
    dims[ 'topSpace' ] = dims[ 'elTop' ];
    dims[ 'rightSpace' ] = dims[ 'winWidth' ] - (dims[ 'elLeft' ] + dims[ 'elWidth' ]);
    dims[ 'botSpace' ] = dims[ 'winHeight' ] - (dims[ 'elTop' ] + dims[ 'elHeight' ]); // How much space below the element
    dims[ 'leftSpace' ] = dims[ 'elLeft' ] - (_o.horizontalPadding * 2);

    // Get the X/Y coordinates for the title
    if (dims[ 'leftSpace' ] > (titleWidth + (_o.horizontalPadding * 2))) { // Check the left side space
      dims[ 'title_x' ] = dims[ 'elLeft' ] - titleWidth - (_o.horizontalPadding * 2);
      dims[ 'title_y' ] = dims[ 'elTop' ];
      if (dims[ 'topSpace' ] > ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3)) {
        dims[ 'title_y' ] = dims[ 'elTop' ] - ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3);
      }
      titleIs = 'left';
    } else if (dims[ 'rightSpace' ] > (titleWidth + (_o.horizontalPadding * 2))) { // Check the right side space
      dims[ 'title_x' ] = dims[ 'elLeft' ] + dims[ 'elWidth' ] + _o.horizontalPadding;
      dims[ 'title_y' ] = dims[ 'elTop' ];
      if (dims[ 'topSpace' ] > ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3)) {
        dims[ 'title_y' ] = dims[ 'elTop' ] - ((titleHeight + descHeight + (_o.verticalPadding * 4)) / 3);
      }
      titleIs = 'right';
    } else if (dims[ 'topSpace' ] > (titleHeight + (_o.verticalPadding * 2))) { // check for space above the element
      dims[ 'title_y' ] = dims[ 'elTop' ] - titleHeight - (_o.verticalPadding * 4);
      dims[ 'title_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth;
      titleIs = 'top';
    } else { // Must go on the bottom.
      dims[ 'title_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth;
      dims[ 'title_y' ] = dims[ 'elTop' ] + dims[ 'elHeight' ] + _o.verticalPadding;
      titleIs = 'bottom';
    }

    // Sets the page scrollTo Y coordinate based on the location of the title.
    if (titleIs !== 'bottom') {
      if ($(window).height() < (dims[ 'elTop' ] + dims[ 'elHeight' ])) {
        dims[ 'scrollTo' ] = dims[ 'title_y' ] - _o.verticalPadding;
      } else {
        dims[ 'scrollTo' ] = 0;
      }
    } else {
      dims[ 'scrollTo' ] = dims[ 'elTop' ] - (_o.verticalPadding * 2);
    }

    // Get the X/Y coordinates for the description/controls
    if (dims[ 'leftSpace' ] >= (descWidth + (_o.horizontalPadding * 2))) { // Check the left side space
      dims[ 'desc_x' ] = dims[ 'elLeft' ] - descWidth - (_o.horizontalPadding * 2);
      dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'left')
          ? titleHeight + _o.verticalPadding
          : 0
      );
    } else if (dims[ 'rightSpace' ] >= (descWidth + (_o.horizontalPadding * 2))) { // Check the right side space
      dims[ 'desc_x' ] = dims[ 'elLeft' ] + dims[ 'elWidth' ] + _o.horizontalPadding;
      dims[ 'desc_y' ] = dims[ 'title_y' ] + (_o.verticalPadding * 2) + ((titleIs === 'right')
          ? titleHeight + _o.verticalPadding
          : 0
      );
    } else if (dims[ 'topSpace' ] >= (descHeight + (_o.verticalPadding * 4) + ((titleIs === 'top')
          ? titleHeight
          : 0)
      ) && titleIs === 'top') { // check for space above the element
      dims[ 'desc_y' ] = dims[ 'elTop' ] - _o.verticalPadding - descHeight - ((titleIs === 'top')
          ? titleHeight + _o.verticalPadding
          : 0
      );
      dims[ 'desc_x' ] = (dims[ 'winWidth' ] / 2) - titleWidth;
      if (titleIs === 'top') {
        dims[ 'title_y' ] -= descHeight;
      }
    } else { // Must go on the bottom.
      dims[ 'desc_x' ] = (dims[ 'winWidth' ] / 2) - descWidth;
      dims[ 'desc_y' ] = dims[ 'elTop' ] + dims[ 'elHeight' ] + (_o.verticalPadding * 2) + ((titleIs === 'bottom')
          ? titleHeight + _o.verticalPadding
          : 0
      );
      if (titleIs === 'right') {
        dims[ 'desc_x' ] = dims[ 'title_x' ] - (descWidth * 0.25);
        dims[ 'desc_y' ] = dims[ 'desc_y' ] + _o.verticalPadding;
      }
    }
  }

  function setupStyle() {
    var styleSheet = '';
    var styles = {
      'overlay' : {
        selector : '#' + _o.prefix + '_tour, #' + _o.prefix + '_overlay',
        style : 'height: 100vh; width: 100%;'
      },
      'tour' : {
        selector : '#' + _o.prefix + '_tour',
        style : 'position: absolute;'
      },
      'title' : {
        selector : '.' + _o.prefix + '_title',
        style : 'top: 0; left: 0; text-shadow: 0 0 10px #aaa; color: white; font-size: 2em; position: absolute; font-weight: bold !important; width: 20rem;'
      },
      'description' : {
        selector : '.' + _o.prefix + '_description',
        style : 'top: 0; left: 0; box-shadow: 0 0 7px #666; border-radius: 7px; padding: 11px; color: white; font-size: 20px; position: absolute; width: 35rem; font-weight: normal !important;'
      },
      'controls' : {
        selector : '.' + _o.prefix + '_controls',
        style : 'top: 0; left: 0; margin: 8px 0; text-align: right; padding: 11px; color: white; font-size: 20px; position: absolute; z-index: 10000; transition: all 0.75s; transition-timing-function: ease-in-out;'
      },
      'control_buttons' : {
        selector : '.' + _o.prefix + '_controls .btn',
        style : 'margin-right: 10px; box-shadow: 0 0 3px white;'
      },
      'targets' : {
        selector : '#' + _o.prefix + '_targets',
        style : 'top: 0; left: 0; border-radius: 50%; border: 1px solid #333; position: absolute; opacity: 0.3;'
      },
      'target_large' : {
        selector : '.' + _o.prefix + '_target',
        style : 'border-radius: 50%; position: relative; height: 80%; width: 80%; top: 9%; padding: 0; margin: 0 auto;'
      },
      'target_small' : {
        selector : '.' + _o.prefix + '_target_small',
        style : 'border: 2px solid #aaa;'
      },
      'target_medium' : {
        selector : '.' + _o.prefix + '_target_medium',
        style : 'border: 2px solid #666;'
      },
      'transitions_main' : {
        selector : '.' + _o.prefix + '_element_transition',
        style : 'transition: all 0.75s; transition-timing-function: ease-in-out;'
      },
      'transition_box' : {
        selector : '.' + _o.prefix + '_box_transition',
        style : 'transition-property: top, left; transition-duration: 0.75s; transition-timing-function: ease-in-out;'
      },
      'transition_key_frame' : {
        selector : '@keyframes targetGlow',
        style : 'from { box-shadow: 0 0 1px rgba(64, 64, 64, 0.78); } to { box-shadow: 0 0 8px white; }'
      },
      'target_glow' : {
        selector : '.' + _o.prefix + '_glow',
        style : 'animation-duration: 1s; animation-name: targetGlow; animation-iteration-count: infinite; animation-direction: alternate;'
      },
      'target_delay_1' : {
        selector : '.' + _o.prefix + '_animation_delay1',
        style : 'animation-delay: 0.3s;'
      },
      'target_delay_2' : {
        selector : '.' + _o.prefix + '_animation_delay2',
        style : 'animation-delay: 0.6s;'
      },
      'spotlight' : {
        selector : '.' + _o.prefix + '_shade',
        style : 'overflow: hidden; transition: all 0.75s; transition-timing-function: ease-in-out; position: absolute; border-radius: 5px;'
      }
    };

    for (var style in styles) {
      if (! styles.hasOwnProperty(style)) return;

      styleSheet += styles[ style ].selector + ' { ';

      if (_o.styleOverwrites.hasOwnProperty(style)) {
        styleSheet += _o.styleOverwrites[ style ];
      } else {
        styleSheet += styles[ style ].style;
      }

      styleSheet += ' }\n';
    }

    elements.style.html(styleSheet);
  }

  function textWidth() {
    var org = $(this);
    var html = $('<span>' + org.html() + '</span>');
    html.css({
      'font-family' : org.css('font-family'),
      'font-size' : org.css('font-size'),
      'postion' : 'absolute',
      'width' : 'auto',
      'left' : '-9999px'
    });
    $('body').append(html);
    var width = html.outerWidth();
    html.remove();
    return width;
  }

  return __init();
};




var PageTour = $.fn.PageTour();
$('#start-tour').on('click', PageTour.open);
&#13;
#tour_targets {
  background-color: #ccc;
}

#tour_tour .tour_title {
  color: red;
}


#tour_tour .tour_description {
  background-color: blue;
  color: white;
}
&#13;
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
  <button id="start-tour" class="btn btn-primary btn-block btn-lg">Start Tour</button>
<div class="box box-primary" data-tour-index="1" data-tour-title="Card Type" data-tour-description="A card will usually be one of multiple similar type items on a page.">

</div>
&#13;
&#13;
&#13;