FEATURE: Use responsive image sizes in post stream (#13343)

This commit is contained in:
Penar Musaraj 2021-06-18 09:15:03 -04:00 committed by GitHub
parent e9e2827636
commit e305365168
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 10 additions and 221 deletions

View File

@ -1,33 +0,0 @@
export default {
name: "ensure-image-dimensions",
after: "mobile",
initialize(container) {
if (!window) {
return;
}
// This enforces maximum dimensions of images based on site settings
// for mobile we use the window width as a safeguard
// This rule should never really be at play unless for some reason images do not have dimensions
const siteSettings = container.lookup("site-settings:main");
let width = siteSettings.max_image_width;
let height = siteSettings.max_image_height;
const site = container.lookup("site:main");
if (site.mobileView) {
width = window.innerWidth - 20;
}
let styles = `max-width:${width}px; max-height:${height}px;`;
if (siteSettings.disable_image_size_calculations) {
styles = "max-width: 100%; height: auto;";
}
const styleTag = document.createElement("style");
styleTag.id = "image-sizing-hack";
styleTag.innerHTML = `#reply-control .d-editor-preview img:not(.thumbnail):not(.ytp-thumbnail-image):not(.emoji), .cooked img:not(.thumbnail):not(.ytp-thumbnail-image):not(.emoji) {${styles}}`;
document.head.appendChild(styleTag);
},
};

View File

@ -4,10 +4,7 @@ import highlightSyntax from "discourse/lib/highlight-syntax";
import lightbox from "discourse/lib/lightbox"; import lightbox from "discourse/lib/lightbox";
import { iconHTML } from "discourse-common/lib/icon-library"; import { iconHTML } from "discourse-common/lib/icon-library";
import { setTextDirections } from "discourse/lib/text-direction"; import { setTextDirections } from "discourse/lib/text-direction";
import { import { nativeLazyLoading } from "discourse/lib/lazy-load-images";
nativeLazyLoading,
setupLazyLoading,
} from "discourse/lib/lazy-load-images";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
export default { export default {
@ -38,11 +35,7 @@ export default {
}); });
} }
if (siteSettings.disable_image_size_calculations) { nativeLazyLoading(api);
nativeLazyLoading(api);
} else {
setupLazyLoading(api);
}
api.decorateCooked( api.decorateCooked(
($elem) => { ($elem) => {

View File

@ -1,89 +1,6 @@
const OBSERVER_OPTIONS = {
rootMargin: "66%", // load images slightly before they're visible
};
// Min size in pixels for consideration for lazy loading // Min size in pixels for consideration for lazy loading
const MINIMUM_SIZE = 150; const MINIMUM_SIZE = 150;
const hiddenData = new WeakMap();
const LOADING_DATA =
"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
// We hide an image by replacing it with a transparent gif
function hide(image) {
image.classList.add("d-lazyload");
image.classList.add("d-lazyload-hidden");
hiddenData.set(image, {
src: image.src,
srcset: image.srcset,
width: image.width,
height: image.height,
className: image.className,
});
image.src = image.dataset.smallUpload || LOADING_DATA;
image.removeAttribute("srcset");
image.removeAttribute("data-small-upload");
}
// Restore an image when onscreen
function show(image) {
let imageData = hiddenData.get(image);
if (imageData) {
const copyImg = new Image();
copyImg.onload = () => {
if (copyImg.srcset) {
image.srcset = copyImg.srcset;
}
image.src = copyImg.src;
image.classList.remove("d-lazyload-hidden");
if (image.onload) {
// don't bother fighting with existing handler
// this can mean a slight flash on mobile
image.parentNode.removeChild(copyImg);
} else {
image.onload = () => {
image.parentNode.removeChild(copyImg);
image.onload = null;
};
}
copyImg.onload = null;
};
if (imageData.srcset) {
copyImg.srcset = imageData.srcset;
}
copyImg.src = imageData.src;
// width of image may not match, use computed style which
// is the actual size of the image
const computedStyle = window.getComputedStyle(image);
const actualWidth = parseInt(computedStyle.width, 10);
const actualHeight = parseInt(computedStyle.height, 10);
copyImg.style.position = "absolute";
copyImg.style.top = `${image.offsetTop}px`;
copyImg.style.left = `${image.offsetLeft}px`;
copyImg.style.width = `${actualWidth}px`;
copyImg.style.height = `${actualHeight}px`;
copyImg.className = imageData.className;
// insert after the current element so styling still will
// apply to original image firstChild selectors
image.parentNode.insertBefore(copyImg, image.nextSibling);
} else {
image.classList.remove("d-lazyload-hidden");
}
}
function forEachImage(post, callback) { function forEachImage(post, callback) {
post.querySelectorAll("img").forEach((img) => { post.querySelectorAll("img").forEach((img) => {
if (img.width >= MINIMUM_SIZE && img.height >= MINIMUM_SIZE) { if (img.width >= MINIMUM_SIZE && img.height >= MINIMUM_SIZE) {
@ -92,36 +9,6 @@ function forEachImage(post, callback) {
}); });
} }
export function setupLazyLoading(api) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const { target } = entry;
if (entry.isIntersecting) {
show(target);
observer.unobserve(target);
}
});
}, OBSERVER_OPTIONS);
api.decorateCookedElement((post) => forEachImage(post, (img) => hide(img)), {
onlyStream: true,
id: "discourse-lazy-load",
});
// IntersectionObserver.observe must be called after the cooked
// content is adopted by the document element in chrome
// https://bugs.chromium.org/p/chromium/issues/detail?id=1073469
api.decorateCookedElement(
(post) => forEachImage(post, (img) => observer.observe(img)),
{
onlyStream: true,
id: "discourse-lazy-load-after-adopt",
afterAdopt: true,
}
);
}
export function nativeLazyLoading(api) { export function nativeLazyLoading(api) {
api.decorateCookedElement( api.decorateCookedElement(
(post) => (post) =>

View File

@ -57,7 +57,6 @@ export default class PostCooked {
this._insertQuoteControls($cookedDiv); this._insertQuoteControls($cookedDiv);
this._showLinkCounts($cookedDiv); this._showLinkCounts($cookedDiv);
this._fixImageSizes($cookedDiv);
this._applySearchHighlight($cookedDiv); this._applySearchHighlight($cookedDiv);
this._decorateAndAdopt(cookedDiv); this._decorateAndAdopt(cookedDiv);
@ -90,44 +89,6 @@ export default class PostCooked {
} }
} }
_fixImageSizes($html) {
if (!this.decoratorHelper || !this.decoratorHelper.widget) {
return;
}
let siteSettings = this.decoratorHelper.widget.siteSettings;
if (siteSettings.disable_image_size_calculations) {
return;
}
const maxImageWidth = siteSettings.max_image_width;
const maxImageHeight = siteSettings.max_image_height;
let maxWindowWidth;
$html.find("img:not(.avatar)").each((idx, img) => {
// deferring work only for posts with images
// we got to use screen here, cause nothing is rendered yet.
// long term we may want to allow for weird margins that are enforced, instead of hardcoding at 70/20
maxWindowWidth =
maxWindowWidth || $(window).width() - (this.attrs.mobileView ? 20 : 70);
if (maxImageWidth < maxWindowWidth) {
maxWindowWidth = maxImageWidth;
}
const aspect = img.height / img.width;
if (img.width > maxWindowWidth) {
img.width = maxWindowWidth;
img.height = parseInt(maxWindowWidth * aspect, 10);
}
// very unlikely but lets fix this too
if (img.height > maxImageHeight) {
img.height = maxImageHeight;
img.width = parseInt(maxWindowWidth / aspect, 10);
}
});
}
_showLinkCounts($html) { _showLinkCounts($html) {
const linkCounts = this.attrs.linkCounts; const linkCounts = this.attrs.linkCounts;
if (!linkCounts) { if (!linkCounts) {

View File

@ -680,11 +680,9 @@ aside.onebox.stackexchange .onebox-body {
color: var(--primary-med-or-secondary-med); color: var(--primary-med-or-secondary-med);
} }
.onebox.xkcd .onebox-body { aside.onebox.xkcd .onebox-body img {
img { float: none;
max-width: 100% !important; max-height: unset;
float: none !important;
}
} }
// pdf onebox // pdf onebox

View File

@ -195,6 +195,11 @@ $quote-share-maxwidth: 150px;
sup sup { sup sup {
top: 0; top: 0;
} }
img:not(.thumbnail):not(.ytp-thumbnail-image):not(.emoji) {
max-width: 100%;
height: auto;
}
} }
// add staff color // add staff color
@ -202,10 +207,6 @@ $quote-share-maxwidth: 150px;
.regular > .cooked { .regular > .cooked {
background-color: var(--highlight-low-or-medium); background-color: var(--highlight-low-or-medium);
padding: 10px; padding: 10px;
img:not(.thumbnail) {
max-width: 100%;
height: auto;
}
} }
.clearfix > .topic-meta-data > .names { .clearfix > .topic-meta-data > .names {
span.user-title { span.user-title {
@ -264,15 +265,6 @@ aside.quote {
} }
} }
blockquote {
// due to #image-sizing-hack large images and lightboxes extend past the
// limits blockquotes. Since #image-sizing-hack is inline, we need to use
// !important here otherwise it won't work.
img {
max-width: 100% !important;
}
}
.quote-controls, .quote-controls,
.quote-controls .d-icon { .quote-controls .d-icon {
color: var(--primary-low-mid-or-secondary-high); color: var(--primary-low-mid-or-secondary-high);

View File

@ -2261,11 +2261,6 @@ uncategorized:
create_revision_on_bulk_topic_moves: create_revision_on_bulk_topic_moves:
default: true default: true
disable_image_size_calculations:
default: false
hidden: true
client: true
user_preferences: user_preferences:
default_email_digest_frequency: default_email_digest_frequency:
enum: "DigestEmailSiteSetting" enum: "DigestEmailSiteSetting"

View File

@ -25,10 +25,6 @@ div.poll {
} }
img { img {
// TODO: remove once disable_image_size_calculations is removed
// needed to override internal styles in image-sizing hack
max-width: 100% !important;
height: auto;
// Hacky way to stop images without width/height // Hacky way to stop images without width/height
// from causing abrupt unintended scrolling // from causing abrupt unintended scrolling
&:not([width]):not(.emoji), &:not([width]):not(.emoji),