diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b8fe5b7f7..0d533c3cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Draft - Use appropriately-sized (50x50) images for product thumbnails on product details page [#1097](https://github.com/bigcommerce/cornerstone/pull/1097) +- Load visible images before images below the fold, and save space for lazy loading images prior to loading them [#1104](https://github.com/bigcommerce/cornerstone/pull/1104) ## 1.9.4 (2017-10-17) - Style optimized checkout new checklist radio buttons [#1088](https://github.com/bigcommerce/cornerstone/pull/1088) diff --git a/assets/scss/components/_components.scss b/assets/scss/components/_components.scss index b81d8e3109..926f14dc15 100644 --- a/assets/scss/components/_components.scss +++ b/assets/scss/components/_components.scss @@ -61,6 +61,9 @@ // Alerts @import "foundation/alerts/component"; +// Lazy Load +@import "foundation/lazyLoad/component"; + // Citadel components // ----------------------------------------------------------------------------- diff --git a/assets/scss/components/citadel/cards/_cards.scss b/assets/scss/components/citadel/cards/_cards.scss index 6f1ac0ad22..346abf3a27 100644 --- a/assets/scss/components/citadel/cards/_cards.scss +++ b/assets/scss/components/citadel/cards/_cards.scss @@ -14,7 +14,7 @@ .card-figure { margin-top: $card-figure-marginTop; - + position: relative; &:hover { // scss-lint:disable NestingDepth @@ -24,6 +24,16 @@ } } +.card-img-container { + max-width: get-width(stencilString('productgallery_size')); +} + +.card-img-container:after { + content: ''; + display: block; + padding-bottom: get-padding(stencilString('productgallery_size')); +} + .card-figcaption { display: none; margin: $card-figcaption-margin; @@ -41,10 +51,10 @@ } .card-image { + @include lazy-loaded-img; border: 0; - display: flex; - margin: auto; width: auto; + max-height: 100%; } .card-title { diff --git a/assets/scss/components/foundation/lazyLoad/_component.scss b/assets/scss/components/foundation/lazyLoad/_component.scss new file mode 100644 index 0000000000..14e7835fb2 --- /dev/null +++ b/assets/scss/components/foundation/lazyLoad/_component.scss @@ -0,0 +1,7 @@ +// ============================================================================= +// LAZY LOAD +// ============================================================================= + + +// Component +@import "lazyLoad"; diff --git a/assets/scss/components/foundation/lazyLoad/_lazyLoad.scss b/assets/scss/components/foundation/lazyLoad/_lazyLoad.scss new file mode 100644 index 0000000000..3d29af00f5 --- /dev/null +++ b/assets/scss/components/foundation/lazyLoad/_lazyLoad.scss @@ -0,0 +1,13 @@ + +.lazyload, .lazyloading { + height:100%; +} + +@mixin lazy-loaded-img() { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; +} diff --git a/assets/scss/components/stencil/account/_account.scss b/assets/scss/components/stencil/account/_account.scss index d9aaffdf39..d352f55a76 100644 --- a/assets/scss/components/stencil/account/_account.scss +++ b/assets/scss/components/stencil/account/_account.scss @@ -43,7 +43,7 @@ } .account-product-image { - width: auto; + @include lazy-loaded-img; } } @@ -74,6 +74,12 @@ width: 70px; } +.account-product-figure:after { + content: ''; + display: block; + padding-bottom: get-padding(stencilString('productthumb_size')); +} + .account-product-download { border-radius: 50%; height: remCalc(33px); diff --git a/assets/scss/components/stencil/cart/_cart.scss b/assets/scss/components/stencil/cart/_cart.scss index be2a9adc98..cc03a410d7 100644 --- a/assets/scss/components/stencil/cart/_cart.scss +++ b/assets/scss/components/stencil/cart/_cart.scss @@ -101,8 +101,8 @@ $cart-item-label-offset: $cart-thumbnail-maxWidth + $cart-item-sp float: left; height: $cart-thumbnail-height; margin-bottom: $cart-item-spacing; - text-align: right; width: grid-calc(4, $total-columns); + position: relative; @include breakpoint("small") { // height: auto; @@ -113,17 +113,28 @@ $cart-item-label-offset: $cart-thumbnail-maxWidth + $cart-item-sp @include breakpoint("medium") { float: none; - text-align: left; width: grid-calc(1, $total-columns); } } +.cart-item-figure:after { + content: ''; + display: block; + height: 0; + width: 100%; + padding-bottom: get-padding(stencilString('productthumb_size')); +} + .cart-item-fixed-image { width: 100%; } .cart-item-image { - width: auto; + @include lazy-loaded-img; + + @include breakpoint("medium") { + margin-left:0; + } } .cart-item-title { @@ -568,8 +579,19 @@ $cart-item-label-offset: $cart-thumbnail-maxWidth + $cart-item-sp .previewCartItem-image { @include grid-column(4, $float: false); - padding: spacing("single"); + padding: 0; text-align: center; + position: relative; + + img { + @include lazy-loaded-img; + } +} + +.previewCartItem-image:after { + content: ''; + display: block; + padding-bottom: get-padding(stencilString('productthumb_size')); } .previewCartItem-content { diff --git a/assets/scss/components/stencil/productView/_productView.scss b/assets/scss/components/stencil/productView/_productView.scss index 91742c19f6..f7aaf6d969 100644 --- a/assets/scss/components/stencil/productView/_productView.scss +++ b/assets/scss/components/stencil/productView/_productView.scss @@ -22,7 +22,7 @@ align-items: center; display: flex; justify-content: center; - margin: 0; + margin: auto; position: relative; @include breakpoint("medium") { @@ -30,17 +30,30 @@ min-width: inherit; } - img { - width: 100%; + + .productView-thumbnails { + margin-top: spacing("half"); } +} + +.productView-img-container { + position: relative; + margin: auto; + max-width: get-width(stencilString('product_size')); + width: 100%; - .productView-image--default { + img { + @include lazy-loaded-img; + max-height: 100%; width: auto; } +} - + .productView-thumbnails { - margin-top: spacing("half"); - } +.productView-img-container:after { + content: ''; + display: block; + height: 0; + width: 100%; + padding-bottom: get-padding(stencilString('product_size')); } .productView-thumbnails { @@ -64,14 +77,9 @@ } img { - bottom: 0; - left: 0; - margin: auto; + @include lazy-loaded-img; max-height: 50px; max-width: 50px; - position: absolute; - right: 0; - top: 0; width: auto; } } diff --git a/assets/scss/components/stencil/writeReview/_writeReview.scss b/assets/scss/components/stencil/writeReview/_writeReview.scss index 1e672ece6b..1333e6a254 100644 --- a/assets/scss/components/stencil/writeReview/_writeReview.scss +++ b/assets/scss/components/stencil/writeReview/_writeReview.scss @@ -18,9 +18,24 @@ } } + .writeReview-form { @include breakpoint("medium") { @include grid-column(6); } } + +.writeReview-productImage-container { + position: relative; + + img { + @include lazy-loaded-img; + } +} + +.writeReview-productImage-container:after { + content: ''; + display: block; + padding-bottom: get-padding(stencilString('product_size')); +} diff --git a/assets/scss/layouts/_layouts.scss b/assets/scss/layouts/_layouts.scss index f7a4fb0722..ad18a92264 100644 --- a/assets/scss/layouts/_layouts.scss +++ b/assets/scss/layouts/_layouts.scss @@ -31,6 +31,9 @@ // Brand grid @import "brands/brandGrid"; +// Brand product list +@import "brands/brand"; + // Product grid @import "products/productGrid"; diff --git a/assets/scss/layouts/blog/_blog.scss b/assets/scss/layouts/blog/_blog.scss index 9f280a9d34..af11d252a6 100644 --- a/assets/scss/layouts/blog/_blog.scss +++ b/assets/scss/layouts/blog/_blog.scss @@ -61,7 +61,19 @@ } .blog-thumbnail { - margin: 0 0 (spacing("base") * 2); + margin: 0 auto (spacing("base") * 2); + position: relative; + max-width: get_width(stencilString('blog_size')); + + img { + @include lazy-loaded-img; + } +} + +.blog-thumbnail:after { + content: ''; + display: block; + padding-bottom: get-padding(stencilString('blog_size')); } .blog-post-figure { diff --git a/assets/scss/layouts/brands/_brand.scss b/assets/scss/layouts/brands/_brand.scss new file mode 100644 index 0000000000..fa45a9e840 --- /dev/null +++ b/assets/scss/layouts/brands/_brand.scss @@ -0,0 +1,24 @@ +.brand-image-container { + position: relative; + max-width: get-width(stencilString('thumb_size')); + + img { + @include lazy-loaded-img; + } +} + +.brand-image-container:after { + content: ''; + display: block; + padding-bottom: get-padding(stencilString('thumb_size')); +} + +.brand { + .card-img-container { + max-width: get-width(stencilString('brand_size')); + } + + .card-img-container:after { + padding-bottom: get-padding(stencilString('brand_size')); + } +} diff --git a/assets/scss/layouts/brands/_brandGrid.scss b/assets/scss/layouts/brands/_brandGrid.scss index 8a806dc8be..b99b809cd2 100644 --- a/assets/scss/layouts/brands/_brandGrid.scss +++ b/assets/scss/layouts/brands/_brandGrid.scss @@ -36,4 +36,8 @@ text-align: center; } } + + .card-figure:after { + padding-bottom: get-padding(stencilString('brand_size')); + } } diff --git a/assets/scss/layouts/header/_header.scss b/assets/scss/layouts/header/_header.scss index 18b53e6364..1471af86c1 100644 --- a/assets/scss/layouts/header/_header.scss +++ b/assets/scss/layouts/header/_header.scss @@ -93,6 +93,10 @@ @include breakpoint("medium") { margin-left: remCalc(40px); } + + .header-logo-image { + right: unset; + } } .header-logo--right { @@ -101,6 +105,10 @@ @include breakpoint("medium") { margin-right: remCalc(40px); } + + .header-logo-image { + left: unset; + } } .header-logo-text { @@ -139,7 +147,22 @@ } } +.header-logo-image-container { + position: relative; +} + +.header-logo-image-container:after { + content: ''; + display: block; + padding-bottom: remCalc($header-height) - $header-logo-marginVertical * 2; + + @include breakpoint("medium") { + padding-bottom: get-height(stencilString('logo_size')); + } +} + .header-logo-image { + @include lazy-loaded-img; max-height: remCalc($header-height) - $header-logo-marginVertical * 2; @include breakpoint("medium") { @@ -147,6 +170,8 @@ } } + + // // Mobile Menu Toggle // diff --git a/assets/scss/tools/_image.scss b/assets/scss/tools/_image.scss new file mode 100644 index 0000000000..236df324fb --- /dev/null +++ b/assets/scss/tools/_image.scss @@ -0,0 +1,41 @@ +/// +/// Calculate the required `padding-bottom` value to reserve page space for an image of a given size, represented by `$size`. +/// +/// @param {String} $size - A string of the form 'XxY', where 'X' and 'Y' are integers. +/// +/// @return {Number} - A percentage indicating the appropriate value of `padding-bottom`. +/// +@function get-padding($size) { + $list: str-split($size, 'x'); + + $width: to-number(nth($list, 1)); + $height: to-number(nth($list, 2)); + + @return percentage($height/$width); +} + +/// +/// Returns a string representing the width (in pixels) of an image with size `$size`. +/// +/// @param {String} $size - A string of the form 'XxY', where 'X' and 'Y' are integers. +/// +/// @return {String} - A string of the form 'Xpx' +/// +@function get-width($size) { + $list: str-split($size, 'x'); + + @return nth($list, 1) + 'px'; +} + +/// +/// Returns a string representing the width (in pixels) of an image with size `$size`. +/// +/// @param {String} $size - A string of the form 'XxY', where 'X' and 'Y' are integers. +/// +/// @return {String} - A string of the form 'Ypx' +/// +@function get-height($size) { + $list: str-split($size, 'x'); + + @return nth($list, 2) + 'px'; +} diff --git a/assets/scss/tools/_string.scss b/assets/scss/tools/_string.scss index 6d5e62f5c3..fcf19e01a8 100644 --- a/assets/scss/tools/_string.scss +++ b/assets/scss/tools/_string.scss @@ -8,3 +8,85 @@ @return $string; } + +// https://stackoverflow.com/questions/32376461/how-to-split-a-string-into-two-lists-of-numbers-in-sass +/// +/// Split `$string` into a list of two strings at the first occurrence of `$separator`. +/// +/// @param {String} $string - The string to be split +/// @param {String} $separator - Character indicating where the split should occur +/// +/// @return {List} - A list of the two created strings. The second string will be null if `$separator` is not found. +/// +@function str-split($string, $separator) { + + $index : str-index($string, $separator); + + @if not $index { + @return $string ""; + } + + $str-1 : str-slice($string, 1, $index - 1); + $str-2 : str-slice($string, $index + 1); + + @return $str-1 $str-2; +} + +// https://www.sassmeister.com/gist/9fa19d254864f33d4a80 +/// +/// Converts a String representation of a number into a Number +/// +/// @param {String} $value - The String to be converted +/// +/// @return {Number} - The converted Number +/// +@function to-number($value) { + @if type-of($value) == 'number' { + @return $value; + } @else if type-of($value) != 'string' { + $_: log('Value for `to-number` should be a number or a string.'); + } + + $result: 0; + $digits: 0; + $minus: str-slice($value, 1, 1) == '-'; + $numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9); + + @for $i from if($minus, 2, 1) through str-length($value) { + $character: str-slice($value, $i, $i); + + @if not (index(map-keys($numbers), $character) or $character == '.') { + @return to-length(if($minus, -$result, $result), str-slice($value, $i)) + } + + @if $character == '.' { + $digits: 1; + } @else if $digits == 0 { + $result: $result * 10 + map-get($numbers, $character); + } @else { + $digits: $digits * 10; + $result: $result + map-get($numbers, $character) / $digits; + } + } + + @return if($minus, -$result, $result);; +} + + +/// +/// Add `$unit` to `$value` +/// +/// @param {Number} $value - Value to add unit to +/// @param {String} $unit - String representation of the unit +/// +/// @return {Number} - `$value` expressed in `$unit` +/// +@function to-length($value, $unit) { + $units: ('px': 1px, 'cm': 1cm, 'mm': 1mm, '%': 1%, 'ch': 1ch, 'pc': 1pc, 'in': 1in, 'em': 1em, 'rem': 1rem, 'pt': 1pt, 'ex': 1ex, 'vw': 1vw, 'vh': 1vh, 'vmin': 1vmin, 'vmax': 1vmax); + + @if not index(map-keys($units), $unit) { + $_: log('Invalid unit `#{$unit}`.'); + } + + @return $value * map-get($units, $unit); +} diff --git a/assets/scss/tools/_tools.scss b/assets/scss/tools/_tools.scss index 6d8ced6935..8258581725 100644 --- a/assets/scss/tools/_tools.scss +++ b/assets/scss/tools/_tools.scss @@ -1,2 +1,3 @@ @import "list"; @import "string"; +@import "image"; diff --git a/templates/components/account/order-contents.html b/templates/components/account/order-contents.html index 24350970fa..d7702c6e41 100644 --- a/templates/components/account/order-contents.html +++ b/templates/components/account/order-contents.html @@ -21,10 +21,12 @@
{{lang 'account.orders.details.ship_to_mu
{{#if type '===' 'giftcertificate'}} - Gift Certificate + Gift Certificate {{else}} - {{/if}} diff --git a/templates/components/account/orders-list.html b/templates/components/account/orders-list.html index 886257a1d8..b8c285b703 100644 --- a/templates/components/account/orders-list.html +++ b/templates/components/account/orders-list.html @@ -6,10 +6,12 @@