This is a short walktrough on how to let your website users upload their own avatars, without using a plugin.

First, define the maximum image sizes (note that we will only downsize, smaller images will get browser-resized):

define('MAX_AVATAR_WIDTH', 96);
define('MAX_AVATAR_HEIGHT', 96);

This goes in the child theme’s functions-user.php file.

Next, create the form which lets users upload the image. I chose the author.php template here, but you can easily add it inside the dashboard trough some hooks.

<?php if(is_user_logged_in() && get_current_user_id() === (int)get_query_var('author')): ?>
<form method="POST" enctype="multipart/form-data" action="">
  <input type="file" name="user_avatar" />
  <input type="hidden" name="action" value="wp_handle_upload" />
  <input type="submit" value="Upload new Avatar" />
</form>
<?php endif; ?>

The actual upload processing. You can have this in the same author template before get_header(), or inside the child theme’s functions:

if(is_user_logged_in() && isset($_FILES['user_avatar'])){

  // we need this for the wp_handle_upload function
  require_once ABSPATH.'wp-admin/includes/file.php';

  // ID of the current user
  $current_user_id = get_current_user_id();

  // just be aware that GIFs are annoying as fuck
  $allowed_image_types = array(
    'jpg|jpeg|jpe' => 'image/jpeg',
    'png'          => 'image/png',
    'gif'          => 'image/gif',
  );

  // let wp do the upload checks, file moving etc.
  $status = wp_handle_upload($_FILES['user_avatar'], array('mimes' => $allowed_image_types));

  // no errors? Get the uploaded file path and resize it
  if(empty($status['error'])){

    // resize it
    $resized = image_resize($status['file'], MAX_AVATAR_WIDTH, MAX_AVATAR_HEIGHT, $crop = true);

    // resize failed, display the reason
    if(is_wp_error($resized))
      wp_die($resized->get_error_message());

    // determine the resized file URL
    $uploads = wp_upload_dir();
    $resized_url = $uploads['url'].'/'.basename($resized);

    // insert the file URL into the current user meta
    update_user_meta($current_user_id, 'custom_avatar', $resized_url);

  // error, show it
  }else{
    wp_die(sprintf(__('Upload Error: %s'), $status['error']));

  }

}

Finally, hook into the avatar display function and get the avatar user meta field:

add_filter('get_avatar', 'custom_avatars', 10, 3);

function custom_avatars($avatar, $id_or_email, $size){
  if(is_user_logged_in()){
    $current_user = wp_get_current_user();
    $image_url = get_user_meta($current_user->ID, 'custom_avatar', true);
    if($user_avatar !== false)
      return '<img src="'.$image_url.'" class="avatar photo" width="'.$size.'" height="'.$size.'" alt="'.$current_user->display_name .'" />';
  }

  return $avatar;
}