使用history.pushState创建CMS AJAX页面转换

2018-04-16 18:35


我将使用history.pushState来解释如何处理Craft CMS的AJAX页面转换的基础知识,首先举例说明如何通过少量修改代码实现的功能:

 

我现在在几个不同的网站上使用了类似的代码变体,而且效果很好。

我将介绍的内容:

  • 创建AJAX模板布局
  • 修改条目模板
  • 添加新的扩展逻辑
  • 包括一个包含所有内容的元素,以进行动画处理
  • 使用 Velocity.js简单淡入/淡出页面转换

什么将取决于你:

  • JavaScript来处理更新HTML标题。这被排除在外,因为它取决于你如何处理它,首先,就SEO而言
  • 任何复杂的页面过渡动画。我建议使用Velocity.js
  • 集成和修改代码以与您的项目一起工作

创建AJAX模板布局

与您的模板中扩展的常规布局相比,您需要创建一个AJAX布局。

您预先存在的  _layout.twig  看起来与此类似:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head>
  {% include '_partials/head' %}
</head>

<body class="preload">

  {% include '_partials/header' %}

  <main class="js-main" role="main">
    {% block content %}
      <p>If you see me, you haven’t set your <code>{% verbatim %}{% block content %}{% endblock %}{% endverbatim %}</code> yet.</p>
    {% endblock %}
  </main>

  {% include '_partials/scripts' %}

</body>
</html>

新的  _ajax-layout.twig  将只是内容块:

{% block content %}
  <p>If you see me, you haven’t set your <code>{% verbatim %}{% block content %}{% endblock %}{% endverbatim %}</code> yet.</p>
{% endblock %}

这是因为我们只想获取每页之间不同的内容。在大多数网站上,标题/导航和页脚保持不变。

修改条目模板

添加新的扩展逻辑

通常情况下,您可以在所有模板中扩展布局文件,如下所示:

{% extends '_layout' %}

但是对于AJAX页面转换,您不想获得整个页面,只需要新内容或新的  _ajax-layout.twig  文件。所以我们做这样的事情:

{% extends (craft.request.isAjax and not craft.request.isLivePreview) ? "_ajax-layout" : "_layout" %}

这将检查请求是否是AJAX而不是实时预览。如果我们没有检查实时预览,那么该功能将无法正常工作。您只会在实时预览中看到条目内容,而不是整个页面。

请注意,这需要是三元运算符,因为根据Twig文档,您不能在模板中扩展多个运算符  

因此,请 使用更新后的AJAX版本替换每个模板中的所有默认  {%extends%}

包装元素,用于动画

我用Velocity.js处理AJAX过渡动画的方式是动画化一个包装每个页面上所有内容的父元素。你可能会找到更适合你的项目的方式,但这对我来说是完美无瑕的。

之前

{# The new extends logic #}
{% extends (craft.request.isAjax and not craft.request.isLivePreview) ? "_layout-ajax" : "_layout" %}

{% block content %}
  {{ entry.body }}

  <h2>Recent Blogs</h2>
  <ul>
    {% for entry in craft.entries.section('blog').limit(5).find() %}
      <li><a href="{{ entry.url }}">{{ entry.title }}</a></li>
    {% endfor %}
  </ul>
{% endblock %}

{# The new extends logic #}
{% extends (craft.request.isAjax and not craft.request.isLivePreview) ? "_layout-ajax" : "_layout" %}

{% block content %}
  <div class="js-ajax-wrapper">

    {{ entry.body }}

    <h2>Recent Blogs</h2>
    <ul>
      {% for entry in craft.entries.section('blog').limit(5).find() %}
        <li><a href="{{ entry.url }}">{{ entry.title }}</a></li>
      {% endfor %}
    </ul>

  </div>
{% endblock %}

JavaScript:使用history.pushState

要在网站上建立链接,请使用  history.pushState

$(function() {
  $(document).on('click', 'a', function() {
    var href = $(this).attr("href");

    history.pushState({}, '', href);
    $main.load(href);
    return false;
  });
});

这使用  history.pushState  将单击的<a>  标记的href属性添加  到浏览器历史记录中。

它返回false以防止浏览器跟随该链接。

请注意,我委派了点击事件。这是因为如果我们做了像......

$('a').on('click', function() {
  var href = $(this).attr("href");

  history.pushState({}, '', href);
  $main.load(href);
  return false;
});

...任何 被点击的新的  <a>标签都会像正常一样运行,并且不会使用  history.pushState这适用于整个JavaScript中的大部分内容。您需要确保所有内容在添加到DOM后仍然有效。

大多数网站都有一致的页眉和页脚,这些页面和页脚不会发生变化,并且是一个主要的内容区域。所以我们需要使用AJAX来用新内容替换现有的内容。

我选择使用 带有.js-main  类的语义  <main>标记  ,它将包含所有被替换和添加的内容:

$(function() {
  var $main = $('.js-main');

  $(document).on('click', 'a', function() {
    var href = $(this).attr("href");

    history.pushState({}, '', href);
    $main.load(href);
    return false;
  });
});

当页面加载时,我们需要能够运行代码,无论是普通页面加载还是AJAX页面加载。所以我们将添加函数来处理这两个:

$(function() {
  var $main = $('.js-main'),

  /* ----- Do this when a page loads ----- */
  init = function() {
    /* ----- This is where I would run any page specific functions ----- */
  },

  /* ----- Do this for ajax page loads ----- */
  ajaxLoad = function(html) {
    init();

    /* ----- Here you could maybe add logic to set the HTML title to the new page title ----- */
  },

/* ----- This runs on the first page load with no ajax ----- */
  init();

  $(document).on('click', 'a', function() {
    var href = $(this).attr("href");

    history.pushState({}, '', href);
    $main.load(href);
    return false;
  });
});

为确保外部链接正常打开,我们需要检查我们是否 仅在内部链接上使用  history.pushState

$(function() {
  var $main = $('.js-main'),

  /* ----- Do this when a page loads ----- */
  init = function() {
    /* ----- This is where I would run any page specific functions ----- */
  },

  /* ----- Do this for ajax page loads ----- */
  ajaxLoad = function(html) {
    init();

    /* ----- Here you could maybe add logic to set the HTML title to the new page title ----- */
  },

  loadPage = function(href) {
    $main.load(href + 'main>*', ajaxLoad);
  };

  /* ----- This runs on the first page load with no ajax ----- */
  init();

  $(document).on('click', 'a', function() {
    var href = $(this).attr("href");

    if (href.indexOf(document.domain) > -1 || href.indexOf(':') === -1) {
      history.pushState({}, '', href);
      loadPage(href);
      return false;
    }
  });
});

现在让我们使浏览器的后退和前进按钮与AJAX转换一起工作。我们将监听  popstate  事件,并确保我们只在事件发生时才采取行动:

$(function() {
  var $main = $('.js-main'),
      changedPage = false,

  /* ----- Do this when a page loads ----- */
  init = function() {
    /* ----- This is where I would run any page specific functions ----- */
  },

  /* ----- Do this for ajax page loads ----- */
  ajaxLoad = function(html) {
    init();

    /* ----- Here you could maybe add logic to set the HTML title to the new page title ----- */

    /* ----- Used for popState event (back/forward browser buttons) ----- */
    changedPage = true;
  },

  loadPage = function(href) {
    $main.load(href + 'main>*', ajaxLoad);
  };

  /* ----- This runs on the first page load with no ajax ----- */
  init();

  $(window).on("popstate", function(e) {
    // -------------------------------------
    //   If there was an AJAX page transition already,
    //   then AJAX page load the requested page from the back or forwards button click.
    //   Variable initially set after the $main variable.
    // -------------------------------------
    if (changedPage) loadPage(location.href);
  });

  $(document).on('click', 'a', function() {
    var href = $(this).attr("href");

    if (href.indexOf(document.domain) > -1 || href.indexOf(':') === -1) {
      history.pushState({}, '', href);
      loadPage(href);
      return false;
    }
  });
});

在这一点上,AJAX页面过渡的一个简单的删除和替换。让我们通过添加一些动画来修饰它。我将 在  Velocity UI Pack中使用  Velocity.js,替换loadPage  函数的内容  

$(function() {
  var $main = $('.js-main'),
      changedPage = false,

  /* ----- Do this when a page loads ----- */
  init = function() {
    /* ----- This is where I would run any page specific functions ----- */
  },

  /* ----- Do this for ajax page loads ----- */
  ajaxLoad = function(html) {
    init();

    /* ----- Here you could maybe add logic to set the HTML title to the new page title ----- */

    /* ----- Used for popState event (back/forward browser buttons) ----- */
    changedPage = true;
  },

  loadPage = function(href) {

    $main.wrapInner('<div class="new-results-div" />');

    /* ----- Set height of $main to ensure the footer doesn't jump up -----  */
    var newResultsHeight = $('.new-results-div').outerHeight();
    $main.height(newResultsHeight);

    $('.js-ajax-wrapper').velocity('transition.fadeOut', {
      /* ----- Upon completion of animating out content put user at top of page. ----- */
      complete: function(){
        $('html').velocity("scroll", {
          duration: 0,
          easing: "ease",
          mobileHA: false
        });
      }
    });

    $.ajax({
      type: 'POST',
      url: href,
      data: {},
      success: function(result){
        /* ----- Where the new content is added ----- */
        $main.html(result);

        /* ----- Wrap content in div so we can get it's height ----- */
        $main.wrapInner('<div class="new-results-div" />');

        /* ----- Get height of new container inside results container and set $main to it so there's no content jumpage -----  */
        var newResultsHeight = $('.new-results-div').outerHeight();
        $main.height(newResultsHeight);

        /* ----- Bring In New Content ----- */
        $('.js-main .js-ajax-wrapper').velocity('transition.fadeIn', {
          visibility: 'visible',
          complete: function() {
            /* ----- Removes the temp height from $main ----- */
            $main.css('height', '');

            ajaxLoad();
          }
        });
      },
      error: function(){
        console.log("error.");
        location.reload();
      }
    });

  };

  /* ----- This runs on the first page load with no ajax ----- */
  init();

  $(window).on("popstate", function(e) {
    // -------------------------------------
    //   If there was an AJAX page transition already,
    //   then AJAX page load the requested page from the back or forwards button click.
    //   Variable initially set after the $main variable.
    // -------------------------------------
    if (changedPage) loadPage(location.href);
  });

  $(document).on('click', 'a', function() {
    var href = $(this).attr("href");

    if (href.indexOf(document.domain) > -1 || href.indexOf(':') === -1) {
      history.pushState({}, '', href);
      loadPage(href);
      return false;
    }
  });
});

这就是处理带有history.pushState的AJAX页面转换的基本代码。此时,您应该能够通过动画AJAX页面转换浏览您的Craft CMS网站。

服务支持

我们珍惜您每一次在线询盘,有问必答,用专业的态度,贴心的服务。

让您真正感受到我们的与众不同!

合作流程
合作流程

重庆网站建设流程从提出需求到网站建设报价,再到网站建设,每一步都是规范和专业的。

常见问题
常见问题

什么是网站定制?网站报价如何?网站常见问题。

常见问题
售后保障

网站建设不难,难的是一如既往的热情服务及技术支持。我们知道:做网站就是做服务,就是做售后。