Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JS Loaders #123

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 103 additions & 29 deletions assets/scripts/app.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,109 @@
import modular from 'modujs';
import * as modules from './modules';
import globals from './globals';
import { html } from './utils/environment';

const app = new modular({
modules: modules
});

window.onload = (event) => {
const $style = document.getElementById('main-css');

if ($style) {
if ($style.isLoaded) {
init();
} else {
$style.addEventListener('load', (event) => {
init();
});
}
} else {
console.warn('The "main-css" stylesheet not found');
import modular from 'modujs'
import * as modules from './modules'
import globals from './globals'
import { fontsLoader, preloadImages, styleSheetsLoader } from './utils/loaders'
import { html } from './utils/environment'
import config from './config'

class App {
constructor() {
console.log(`${this.constructor.name}:constructor`)

this.options = Object.freeze({
fonts: [
// { name: '<font-name>', style: '<font-style>', weight: '<font-weight>' }
],
preloadImage: [
config.SELECTORS.IMAGE_PRELOAD
],
styleSheets: [
config.SELECTORS.MAIN_STYLESHEET
]
})

// Create app
this.moduleManager = new modular({
modules: modules
})

// this.addCustomEvents()

this.setInitialVars()
}

load() {
console.log(`${this.constructor.name}:load`)

// Font load
const fontLoad = new Promise(resolve => {
fontsLoader(this.options.fonts, () => {
html.classList.add(config.CSS_CLASS.FONTS_LOADED)
resolve()
})
})

// Image preload
const imagePreload = new Promise(resolve => {
preloadImages(this.options.preloadImage, () => {
html.classList.add(config.CSS_CLASS.IMAGES_PRELOADED)
resolve()
})
})

// Stylesheets load
const styleSheetLoad = new Promise(resolve => styleSheetsLoader(this.options.styleSheets, () => resolve()))

Promise.all([
fontLoad,
imagePreload,
styleSheetLoad,
]).then(() => {
this.init()
}).catch(e => {
console.log(e)
})
}

init() {
console.log(`${this.constructor.name}:init`)

// Init globals
globals()

// Init modular app
this.moduleManager.init(this.moduleManager)

// Update classes
html.classList.add(config.CSS_CLASS.LOADED)
html.classList.add(config.CSS_CLASS.READY)
html.classList.remove(config.CSS_CLASS.LOADING)
}

addCustomEvents() {
console.log(`${this.constructor.name}:addCustomEvents`)
}
};

function init() {
globals();
/*
* Set initial variables.
*/

app.init(app);
setInitialVars() {

html.classList.add('is-loaded');
html.classList.add('is-ready');
html.classList.remove('is-loading');
/**
* Store the initial viewport height in a CSS property.
*
* @see {@link https://css-tricks.com/the-trick-to-viewport-units-on-mobile/}
* This can be applied to elements, instead of using the `vh` unit,
* for consistent and correct sizing on mobile browsers.
*
* @see {@link https://caniuse.com/viewport-unit-variants}
* This trick should be replaced with viewport-relative units
* once browser support has improved.
*/

html.style.setProperty('--vh-initial', `${0.01 * html.clientHeight}px`)
}
}

const app = new App()
window.addEventListener('load', () => app.load(), { once: true })
25 changes: 25 additions & 0 deletions assets/scripts/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const env = process.env.NODE_ENV

export default config = Object.freeze({
// Environments
ENV: env,
IS_PROD: env === 'production',
IS_DEV: env === 'development',

// CSS class names
CSS_CLASS: {
LOADING: 'is-loading',
READY: 'is-ready',
LOADED: 'is-loaded',
FONTS_LOADED: 'has-fonts-loaded',
IMAGES_PRELOADED: 'has-images-preloaded',
LAZY_LOADED: 'is-lazy-loaded',
},

// JS selectors
SELECTORS: {
IMAGE_LAZY: '.c-lazy',
IMAGE_PRELOAD: 'img[data-preload]',
MAIN_STYLESHEET: '#main-css'
}
})
2 changes: 1 addition & 1 deletion assets/scripts/modules/Scroll.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { module } from 'modujs';
import { lazyLoadImage } from '../utils/image';
import { lazyLoadImage } from '../utils/loaders';
import LocomotiveScroll from 'locomotive-scroll';

export default class extends module {
Expand Down
90 changes: 11 additions & 79 deletions assets/scripts/utils/image.js
Original file line number Diff line number Diff line change
@@ -1,87 +1,19 @@
const LAZY_LOADED_IMAGES = []

export function loadImage(url, options = {}) {
return new Promise((resolve, reject) => {
const $img = new Image();

if (options.crossOrigin) {
$img.crossOrigin = options.crossOrigin;
}

const loadCallback = () => {
resolve({
element: $img,
...getImageMetadata($img),
});
}

if($img.decode) {
$img.src = url
$img.decode().then(loadCallback).catch(e => {
reject(e)
})
} else {
$img.onload = loadCallback
$img.onerror = (e) => {
reject(e);
};
$img.src = url
}
});
}

export function getImageMetadata($img) {
return {
url: $img.src,
width: $img.naturalWidth,
height: $img.naturalHeight,
ratio: $img.naturalWidth / $img.naturalHeight,
};
}

/**
* Lazy load the given image.
* Get an image meta data
*
* @param {HTMLImageElement} $el - The image element.
* @param {?string} url - The URI to lazy load into $el.
* If falsey, the value of the `data-src` attribute on $el will be used as the URI.
* @param {?function} callback - A function to call when the image is loaded.
* @param {HTMLImageElement} $img - The image element.
* @return {object} The given image meta data
*/
export async function lazyLoadImage($el, url, callback) {
let src = url ? url : $el.dataset.src

let loadedImage = LAZY_LOADED_IMAGES.find(image => image.url === src)

if (!loadedImage) {
loadedImage = await loadImage(src)

if (!loadedImage.url) {
return;
}

LAZY_LOADED_IMAGES.push(loadedImage)
}

if($el.src === src) {
return
}

if ($el.tagName === 'IMG') {
$el.src = loadedImage.url;
} else {
$el.style.backgroundImage = `url(${loadedImage.url})`;
}

requestAnimationFrame(() => {
let lazyParent = $el.closest('.c-lazy');
const getImageMetadata = $img => ({
url: $img.src,
width: $img.naturalWidth,
height: $img.naturalHeight,
ratio: $img.naturalWidth / $img.naturalHeight,
})

if(lazyParent) {
lazyParent.classList.add('-lazy-loaded')
lazyParent.style.backgroundImage = ''
}

$el.classList.add('-lazy-loaded')

callback?.()
})
export {
getImageMetadata,
}
Loading