Magento 2's default PDP image rendering appears to use AJAX to load product images as part of it's default template. This results in the main product image not being visible until Javascript is completely processed. In my experience, the product image can be the very last thing to load due to the delay. Create the following file in your theme: {theme_dir}/Magento_Catalog/templates/product/view/gallery.phtml containing the below code:
<div class="gallery-placeholder _block-content-loading" data-gallery-role="gallery-placeholder">
<div data-role="loader" class="loading-mask">
<div class="loader">
<img src="<?php echo $block->getGalleryImages()->getFirstItem()->getData('medium_image_url') ?>" style="max-width: 100%; max-height: 100%">
<img src="<?php /* @escapeNotVerified */ echo $block->getViewFileUrl('images/loader-1.gif'); ?>"
alt="<?php /* @escapeNotVerified */ echo __('Loading...') ?>">
</div>
</div>
</div>
<!--Fix for jumping content. Loader must be the same size as gallery.-->
<script>
var config = {
"width": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_medium', 'width'); ?>,
"thumbheight": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_small', 'height')
?: $block->getImageAttribute('product_page_image_small', 'width'); ?>,
"navtype": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/navtype"); ?>",
"height": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_medium', 'height'); ?>
},
thumbBarHeight = 0,
loader = document.querySelectorAll('[data-gallery-role="gallery-placeholder"] [data-role="loader"]')[0];
if (config.navtype === 'horizontal') {
thumbBarHeight = config.thumbheight;
}
loader.style.paddingBottom = ( config.height / config.width * 100) + "%";
</script>
<script type="text/x-magento-init">
{
"[data-gallery-role=gallery-placeholder]": {
"mage/gallery/gallery": {
"mixins":["magnifier/magnify"],
"magnifierOpts": <?php /* @escapeNotVerified */ echo $block->getMagnifier(); ?>,
"data": <?php /* @escapeNotVerified */ echo $block->getGalleryImagesJson(); ?>,
"options": {
"nav": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/nav"); ?>",
<?php if (($block->getVar("gallery/loop"))): ?>
"loop": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/loop"); ?>,
<?php endif; ?>
<?php if (($block->getVar("gallery/keyboard"))): ?>
"keyboard": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/keyboard"); ?>,
<?php endif; ?>
<?php if (($block->getVar("gallery/arrows"))): ?>
"arrows": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/arrows"); ?>,
<?php endif; ?>
<?php if (($block->getVar("gallery/allowfullscreen"))): ?>
"allowfullscreen": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/allowfullscreen"); ?>,
<?php endif; ?>
<?php if (($block->getVar("gallery/caption"))): ?>
"showCaption": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/caption"); ?>,
<?php endif; ?>
"width": "<?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_medium', 'width'); ?>",
"thumbwidth": "<?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_small', 'width'); ?>",
<?php if ($block->getImageAttribute('product_page_image_small', 'height') || $block->getImageAttribute('product_page_image_small', 'width')): ?>
"thumbheight": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_small', 'height')
?: $block->getImageAttribute('product_page_image_small', 'width'); ?>,
<?php endif; ?>
<?php if ($block->getImageAttribute('product_page_image_medium', 'height') || $block->getImageAttribute('product_page_image_medium', 'width')): ?>
"height": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_medium', 'height')
?: $block->getImageAttribute('product_page_image_medium', 'width'); ?>,
<?php endif; ?>
<?php if ($block->getVar("gallery/transition/duration")): ?>
"transitionduration": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/transition/duration"); ?>,
<?php endif; ?>
"transition": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/transition/effect"); ?>",
<?php if (($block->getVar("gallery/navarrows"))): ?>
"navarrows": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/navarrows"); ?>,
<?php endif; ?>
"navtype": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/navtype"); ?>",
"navdir": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/navdir"); ?>"
},
"fullscreen": {
"nav": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/nav"); ?>",
<?php if ($block->getVar("gallery/fullscreen/loop")): ?>
"loop": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/loop"); ?>,
<?php endif; ?>
"navdir": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/navdir"); ?>",
<?php if ($block->getVar("gallery/transition/navarrows")): ?>
"navarrows": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/navarrows"); ?>,
<?php endif; ?>
"navtype": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/navtype"); ?>",
<?php if ($block->getVar("gallery/fullscreen/arrows")): ?>
"arrows": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/arrows"); ?>,
<?php endif; ?>
<?php if ($block->getVar("gallery/fullscreen/caption")): ?>
"showCaption": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/caption"); ?>,
<?php endif; ?>
<?php if ($block->getVar("gallery/fullscreen/transition/duration")): ?>
"transitionduration": <?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/transition/duration"); ?>,
<?php endif; ?>
"transition": "<?php /* @escapeNotVerified */ echo $block->getVar("gallery/fullscreen/transition/effect"); ?>"
},
"breakpoints": <?php /* @escapeNotVerified */ echo $block->getBreakpoints(); ?>
}
}
}
</script>