skip to Main Content

Trying to figure out how to do this with plain javascript and css, below is code working with jquery.

In this example a user clicks a link, the script checks to see if there is content in a div, if there is, it fades out the content, then loads by ajax the new content and fades it in.

I know I can toggle a css class, but wondering how to go about using a callback to see when the css animation has completed to be able to fire the ajax request and then fade in.

<section>
    <a href="#" data-file="data1">Load data 1</a>
    <a href="#" data-file="data2">Load data 2</a>
    <div id="content"></div>
</section>

$(document).ready(function(){
    $('body').on('click','a',function(event){
        event.preventDefault();
        var datafile = $(this).data('file');
        console.log(datafile);
        if($('#content').html().length){
            $('#content').fadeOut(700,function(){
                $('#content').load(datafile + '.php').hide().fadeIn(700);
                console.log('has content, fadeout then load fadein ' + datafile);
            })
        } else {
            $('#content').load(datafile + '.php').hide().fadeIn(700);
            console.log('no content, load fadein ' + datafile);
        }
    });
});

contents of data1.php and data2.php are filled with lorem ipsum just for testing purposes, in production they would be interface screens for a cms.

here is a jsfiddle
https://jsfiddle.net/nomadwebdesign/cfvd6uk4/

Looking at Dan Dascalescu’s answer and how to expand upon this How to do fade-in and fade-out with JavaScript and CSS

I’ve also been trying to use the on(‘transitionend’) event listener, however it goes into a loop because after loading by ajax I remove the css class causing the transition again.

I am able to do this by using setTimeout and matching the transition duration however this seems flaky.

At this point I would just like to know how to do this with jQuery and CSS without using fadeIn() and fadeOut()

the sequence is
click -> fade out previous content -> fade in ajax loaded content

UPDATE
Here is a working solution using jQuery without fadeIn() or fadeOut() but with CSS opacity transition

<section id="section2">
    <a href="#" data-file="data1">Load data 1</a>
    <a href="#" data-file="data2">Load data 2</a>
    <div id="dataContent"></div>
</section>

$(document).ready(function(){
    $('body').on('click','#section2 a',function(event){
        event.preventDefault();
        var datafile = $(this).data('file');
        var dataContent = $('#dataContent');
        // if there is content fade it out first
        if(dataContent.html().length){
            dataContent.addClass('opa fade');
            // check for completed transition
            dataContent.one('transitionend', function(){
                dataContent.load(datafile + '.php', function(){
                    dataContent.removeClass('fade');
                });
            }); 
        } else {
            // if there is no content just load and fade in
            dataContent.addClass('fade');
            dataContent.load(datafile + '.php', function(){
                dataContent.removeClass('fade');
                dataContent.addClass('opa');
            });
        }
    });
});

#dataContent.opa {
    opacity: 1;
    transition: opacity .700s; 
}
#dataContent.fade {
  opacity: 0;
}

Still looking for a solution to this without jQuery using vanilla javaScript

referencing these posts for help

https://blog.teamtreehouse.com/using-jquery-to-detect-when-css3-animations-and-transitions-end

https://jonsuh.com/blog/detect-the-end-of-css-animations-and-transitions-with-javascript/

After spending all day thoughtfully researching this and pulling everything I could find from the web together I have a working solution. This is an example using plain vanilla JavaScript Ajax and CSS3 transitions. I have found this to be a popular trend in the last few years (2017-2019) as browsers have advanced an older browsers have fallen away. Also, with the dependency upon jQuery less and less it seems more people are looking for plain js versions and leveraging css3 transitions and animations. I believe this question and answer to be of more detail than the others linked here on stackoverflow and believe this example will be of use to many others.

Here is the working solution

css
#dataContent.opa {
    opacity: 1;
    transition: opacity .400s; 
}
#dataContent.fade {
    opacity: 0;
}
html
<section id="section">
    <a href="#" class="clicker" data-file="data1">Load data 1</a>
    <a href="#" class="clicker" data-file="data2">Load data 2</a>
    <a href="#" class="clicker" data-file="data3">Load data 3</a>
    <div id="dataContent"></div>
</section>
var classname = document.getElementsByClassName('clicker');
ajaxf = function(event){
    var xhttp = new XMLHttpRequest();
    event.preventDefault();
    var datafile = this.getAttribute('data-file');
    var dataContent = document.getElementById('dataContent');
    if(dataContent.innerHTML.length){
        dataContent.classList.add('opa','fade');
        dataContent.addEventListener('transitionend',handler);
        function handler(event) {
            event.target.removeEventListener(event.type,arguments.callee);
            xhttp.onreadystatechange = function(){
                if(this.readyState == 4 && this.status == 200){
                    dataContent.innerHTML = this.responseText;
                    dataContent.classList.remove('fade');
                };
                if(this.status == 404){
                    dataContent.classList.remove('fade');
                    dataContent.innerHTML = 'there was an error retrieving data';
                }               
            };
            xhttp.open('GET',datafile + '.php',true);
            xhttp.send();
        }
    } else {
        dataContent.classList.add('fade');
        xhttp.onreadystatechange = function(){
            if(this.readyState == 4 && this.status == 200){
                dataContent.innerHTML = this.responseText;
                dataContent.classList.remove('fade');
                dataContent.classList.add('opa');
            };
            if(this.status == 404){
                dataContent.innerHTML = 'there was an error retrieving data';
                dataContent.classList.remove('fade');
                dataContent.classList.add('opa');
            }
        };
        xhttp.open('GET',datafile + '.php',true);
        xhttp.send();
    }
};
for(var i = 0; i < classname.length; i++){
    classname[i].addEventListener('click',ajaxf,false);
};

One of the main keys in this process is creating an event listener for the transitionend and then removing the event listener (to repeat the function when the 2nd transition fires)

enter image description here

4

Answers


  1. Chosen as BEST ANSWER
    <style>
    #dataContent.opa {
        opacity: 1;
        transition: opacity .400s; 
    }
    #dataContent.fade {
        opacity: 0;
    }
    </style>
    
    <section id="section">
        <a href="#" class="clicker" data-file="data1">Load data 1</a>
        <a href="#" class="clicker" data-file="data2">Load data 2</a>
        <a href="#" class="clicker" data-file="data3">Load data 3</a>
        <div id="dataContent"></div>
    </section>
    
    <script>
    var classname = document.getElementsByClassName('clicker');
    ajaxf = function(event){
        var xhttp = new XMLHttpRequest();
        event.preventDefault();
        var datafile = this.getAttribute('data-file');
        var dataContent = document.getElementById('dataContent');
        if(dataContent.innerHTML.length){
            dataContent.classList.add('opa','fade');
            dataContent.addEventListener('transitionend',handler);
            function handler(event) {
                event.target.removeEventListener(event.type,arguments.callee);
                xhttp.onreadystatechange = function(){
                    if(this.readyState == 4 && this.status == 200){
                        dataContent.innerHTML = this.responseText;
                        dataContent.classList.remove('fade');
                    };
                    if(this.status == 404){
                        dataContent.classList.remove('fade');
                        dataContent.innerHTML = 'there was an error retrieving data';
                    }               
                };
                xhttp.open('GET',datafile + '.php',true);
                xhttp.send();
            }
        } else {
            dataContent.classList.add('fade');
            xhttp.onreadystatechange = function(){
                if(this.readyState == 4 && this.status == 200){
                    dataContent.innerHTML = this.responseText;
                    dataContent.classList.remove('fade');
                    dataContent.classList.add('opa');
                };
                if(this.status == 404){
                    dataContent.innerHTML = 'there was an error retrieving data';
                    dataContent.classList.remove('fade');
                    dataContent.classList.add('opa');
                }
            };
            xhttp.open('GET',datafile + '.php',true);
            xhttp.send();
        }
    };
    for(var i = 0; i < classname.length; i++){
        classname[i].addEventListener('click',ajaxf,false);
    };
    </script>
    

    One of the main keys in this process is creating an event listener for the transitionend and then removing the event listener (to repeat the function when the 2nd transition fires).

    This method checks to see if there is content in the div and if so it will first fade that content out (using CSS3) and then do an ajax call and fade the new content in. If there is no content it will simply do the ajax call with a fade in.


  2. You can use a simpler and modern approach to ajax using fetch API. Also, using a combination of data-* attribute and CSS simplify fading in and out.

    Set this example:

    var classname = document.getElementsByClassName('clicker');
    let dataContent = document.getElementById("dataContent");
    
    /*
     * Manage transtion in and out in sucess and error callback
     */
    function transition(content) {
      dataContent.classList.add("fade");
    
      var clickFunction = function(event) {
        dataContent.innerHTML = content;
        dataContent.classList.remove("fade");
      };
    
      dataContent.addEventListener("transitionend", clickFunction);
    
      return true;
    }
    
    // Main function, to call fetch
    function ajaxf() {
      var datafile = this.getAttribute('data-file');
      var opts = {
        method: 'GET'
      };
    
    
      fetch(datafile, opts).then(function(response) {
          /* If we dont get an OK response (200) then through an error to
           trigger catch callback
           */
          if (response.status !== 200)
            throw new Error("Not 200 response")
    
          /* Parse response to json. You can use text() as well
           *	https://developer.mozilla.org/en-US/docs/Web/API/Body
           */
          return response.json();
        })
        .then(function(content) {
          // check if content has been set previously
          let hasContent = dataContent.getAttribute("data-hascontent");
    
          if (hasContent != 'false')
            return transition(content.body);
    
          // Process empty div
          dataContent.innerHTML = content.body;
          dataContent.setAttribute("data-hascontent", "true");
    
        }).catch(function(error) {
          // Same as previous methid
          let hasContent = dataContent.getAttribute("data-hascontent");
    
          if (hasContent != 'false')
            return transition(error);
    
          dataContent.innerHTML = error;
          dataContent.setAttribute("data-hascontent", "true");
        });
    }
    
    // Attach Events
    for (var i = 0; i < classname.length; i++) {
      classname[i].addEventListener('click', ajaxf, false);
    }
    #dataContent {
      opacity: 0;
      transition: opacity .400s;
    }
    
    #dataContent[data-hascontent='true'] {
      opacity: 1;
    }
    
    #dataContent[data-hascontent='true'].fade {
      opacity: 0;
    }
    <section id="section">
      <a href="#" class="clicker" data-file="https://jsonplaceholder.typicode.com/posts/1">Load data 1</a>
      <a href="#" class="clicker" data-file="data2">Load data 2</a>
      <a href="#" class="clicker" data-file="https://jsonplaceholder.typicode.com/posts/5">Load data 3</a>
      <div id="dataContent" data-hascontent='false'></div>
    </section>
    Login or Signup to reply.
  3. you can use this URL:
    https://css-tricks.com/dynamic-page-replacing-content/
    , there’s a Demo in it (https://css-tricks.com/examples/DynamicPage/) that just does what do you want

    EDIT:
    have you tried to add a class name to your code container with animation set for a specific time then remove that class name and a class name with a class name to hide the content when replace the content using AJAX then remove the class came and add a class name for the appearing animations?

    Login or Signup to reply.
  4. You could use for this purpose,

    The difference with your approach is that fetch starts on animationstart event and the result is appended to #dataContent element upon animationend event;

    The example below is something you could use for your onclick logic.

    document.querySelectorAll('#section2 a').forEach((item) => {
      item.addEventListener('click', (e) => {
        let dataFile = e.target.dataset.file, 
        dataContent = document.getElementById('dataContent'), 
        myText = null;
        dataContent.classList.add('fade');
        dataContent.addEventListener('animationstart', (ev) => {
          fetch(dataFile).then(async(response) => {
            await response.text().then(async(text) => {
              myText = await text;
            });
          }).catch(async(error) => {
            myText = await error.toString().split(': ')[1];
          });
        });
        dataContent.addEventListener('animationend', (ev) => {
          dataContent.classList.remove('fade');
          dataContent.innerHTML = myText;
        });
      });
    });
    #dataContent.fade {
      animation: fade .950s;
    }
    
    @keyframes fade {
      0% {
        opacity: 1;
      }
      100% {
        opacity: 0;
      }
    }
    <section id="section2">
      <a href="#" data-file="https://www.w3.org/TR/PNG/iso_8859-1.txt">Load data 1</a>
      <a href="#" data-file="http://25.io/toau/audio/sample.txt">Load data 2</a>
      <div id="dataContent">Click any of the above links to retrieve new content!</div>
    </section>

    EDIT

    The same fade-out effect can be achieved using:

    document.querySelectorAll('#section2 a').forEach((item) => {
      item.addEventListener('click', (e) => {
        let dataFile = e.target.dataset.file, 
        dataContent = document.getElementById('dataContent'), 
        myText = null;
        dataContent.classList.add('fade');
        dataContent.addEventListener('transitionstart', (ev) => {
          fetch(dataFile).then(async(response) => {
            await response.text().then(async(text) => {
              myText = await text;
            });
          }).catch(async(error) => {
            myText = await error.toString().split(': ')[1];
          });
        });
        dataContent.addEventListener('transitionend', (ev) => {
          dataContent.classList.remove('fade');
          dataContent.innerHTML = myText;
        });
      });
    });
    #dataContent.fade {
      opacity: 0;
      transition: opacity .950s;
    }
    #dataContent {
      opacity: 1;
    }
    <section id="section2">
      <a href="#" data-file="https://www.w3.org/TR/PNG/iso_8859-1.txt">Load data 1</a>
      <a href="#" data-file="http://25.io/toau/audio/sample.txt">Load data 2</a>
      <div id="dataContent">Click any of the above links to retrieve new content!</div>
    </section>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search