WP_Query with ajax handler returns the same posts

Caveat, I can’t really test this right now. However, there are several things that I think could be improved from your code.

First if we read the codex or some tips on using ajax we can setup an ajax callback function that isn’t pointing to a file where we have to bootstrap the WordPress load functions.

I will make the assumption that all your script is in a JS file and being properly enqueued:

// embed the javascript file that makes the AJAX request
wp_enqueue_script( 'wpa-95258-ajax-request', plugin_dir_url( __FILE__ ) . 'js/ajax.js', array( 'jquery' ) );

// declare the URL to the file that handles the AJAX request (wp-admin/admin-ajax.php)
wp_localize_script( 'wpa-95258-ajax-request', 'wpa-95258-ajax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );

Using wp_localize_script we can pass some variables to a script object that will then be available in the script. We’ll use this for the .ajax URL parameter. We also need an action parameter, because WordPress will look for

// this hook is fired if the current viewer is not logged in
do_action( 'wp_ajax_nopriv_' . $_REQUEST['action'] );

// if logged in:
do_action( 'wp_ajax_' . $_POST['action'] );

So we can use that later on to attach a callback function to any ajax action. First I’ve adapted your .ajax to have an action and use the WordPress ajax url:

function _batchhandler() { 
    var getamount = localStorage.getItem('amount'); 

    console.log('amount of posts to retrive ' + JSON.parse(getamount));

    // Ajax call
    $.ajax({
        type: 'GET',
        data: {
            posts: getamount, 
            page: page,
            category: 'work',
            action: 'batchhandler' // use this to add a callback
        },
        dataType: 'html',
        url: wpa-95258-ajax.ajaxurl, //from localize script
        beforeSend: function() {
            _setHeader;
            if( page != 1 ) {
                console.log('Loading');
                // Show the preloader
                $('body').prepend('<div class="preloader"><span class="rotate"></span></div>');
            }
            // If we reach the end we hide the show more button
            if( page >= total ) {
                $('.load').hide();
            }
        },
        success: function(data) {
            console.log(page);  
            var scroll = ($('.thumb').height() * posts);
            // If thumbs exist append them
            if( data.length ) { 
                // Append the data
                $('#batch').append(data);

                // Remove the crappy width and height attrs from the image * Generated by WP *
                $('img').removeAttr('height').removeAttr('width');

                // Animate each new object in a nice way
                (function _showitem() {
                    $('#batch .thumb:hidden:first').addClass('show', 80, _showitem);

                    // On the last request do load any more
                    loading = false;  
                })();

                // Remove the preloader
                $('.preloader').fadeOut(200, function() {
                    $('.preloader').remove();   
                });
            }
            // return false;
        },
        complete: function() {
            // Delete storage
            localStorage.clear();

            // Update the scroller to match the updated content length
            if (scroller)
                setTimeout("scroller.refresh()", 300);

            // Initalize the load more button
            _clickhandler();
        },
        error: function() {
            console.log('No page found');
        }
    });
}

Now that we have a hook to attach to we can build the callback function without the bootstrapped loader. I’d also recommend getting rid of query_posts(). That does wonky things to the query and it is generally recommended around here to use new WP_Query.

I’ve adjusted your batchloop.php to be a callback function instead:

function wp_ajax_batchhandler_callback(){

    // Our variables  
    $posts = (isset($_GET['numPosts'])) ? $_GET['numPosts'] : 0;  
    $page = (isset($_GET['pageNumber'])) ? $_GET['pageNumber'] : 0;  
    $category = (isset($_GET['category_name'])) ? $_GET['category_name'] : 0;  

    var_dump($posts);

    $args = array(  
        'posts_per_page' => $posts,
        'category_name'  => $category, 
        'post_status'    => 'publish',
        'orderby'        => 'date',
        'order'          => 'DESC',
        'paged'          => $page 
    );

    $ajax_query = new WP_query($args);

    // our loop  
    if ($ajax_query->have_posts()) {  

        while ($ajax_query->have_posts()){  
            $ajax_query->the_post();  
            get_template_part( 'thumbs', get_post_format() );  
        }  
    } 

    // unset($page, $posts, $category);

    wp_reset_postdata();

}
add_action( 'wp_ajax_nopriv_batchhandler', 'batchhandler_callback' ); //logged out users
add_action( 'wp_ajax_batchhandler', 'batchhandler_callback' ); //logged in users

Like I said, I didn’t test this but it looks like you are using:

$page = (isset($_GET['pageNumber'])) ? $_GET['pageNumber'] : 0; 

To get the page number that you are using in the query. However, you aren’t sending the pageNumber in the .ajax function so I doubt you’ll get proper pagination. However, you should now be hooked up with the appropriate way to make ajax calls in WordPress, and I think pagination can be fixed by passing the pageNumber variable.