FEATURE: Use responsive image sizes in post stream (#13343)
This commit is contained in:
parent
e9e2827636
commit
e305365168
|
@ -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);
|
||||
},
|
||||
};
|
|
@ -4,10 +4,7 @@ import highlightSyntax from "discourse/lib/highlight-syntax";
|
|||
import lightbox from "discourse/lib/lightbox";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { setTextDirections } from "discourse/lib/text-direction";
|
||||
import {
|
||||
nativeLazyLoading,
|
||||
setupLazyLoading,
|
||||
} from "discourse/lib/lazy-load-images";
|
||||
import { nativeLazyLoading } from "discourse/lib/lazy-load-images";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
export default {
|
||||
|
@ -38,11 +35,7 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (siteSettings.disable_image_size_calculations) {
|
||||
nativeLazyLoading(api);
|
||||
} else {
|
||||
setupLazyLoading(api);
|
||||
}
|
||||
|
||||
api.decorateCooked(
|
||||
($elem) => {
|
||||
|
|
|
@ -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
|
||||
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) {
|
||||
post.querySelectorAll("img").forEach((img) => {
|
||||
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) {
|
||||
api.decorateCookedElement(
|
||||
(post) =>
|
||||
|
|
|
@ -57,7 +57,6 @@ export default class PostCooked {
|
|||
|
||||
this._insertQuoteControls($cookedDiv);
|
||||
this._showLinkCounts($cookedDiv);
|
||||
this._fixImageSizes($cookedDiv);
|
||||
this._applySearchHighlight($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) {
|
||||
const linkCounts = this.attrs.linkCounts;
|
||||
if (!linkCounts) {
|
||||
|
|
|
@ -680,11 +680,9 @@ aside.onebox.stackexchange .onebox-body {
|
|||
color: var(--primary-med-or-secondary-med);
|
||||
}
|
||||
|
||||
.onebox.xkcd .onebox-body {
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
float: none !important;
|
||||
}
|
||||
aside.onebox.xkcd .onebox-body img {
|
||||
float: none;
|
||||
max-height: unset;
|
||||
}
|
||||
|
||||
// pdf onebox
|
||||
|
|
|
@ -195,6 +195,11 @@ $quote-share-maxwidth: 150px;
|
|||
sup sup {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
img:not(.thumbnail):not(.ytp-thumbnail-image):not(.emoji) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// add staff color
|
||||
|
@ -202,10 +207,6 @@ $quote-share-maxwidth: 150px;
|
|||
.regular > .cooked {
|
||||
background-color: var(--highlight-low-or-medium);
|
||||
padding: 10px;
|
||||
img:not(.thumbnail) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.clearfix > .topic-meta-data > .names {
|
||||
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 .d-icon {
|
||||
color: var(--primary-low-mid-or-secondary-high);
|
||||
|
|
|
@ -2261,11 +2261,6 @@ uncategorized:
|
|||
create_revision_on_bulk_topic_moves:
|
||||
default: true
|
||||
|
||||
disable_image_size_calculations:
|
||||
default: false
|
||||
hidden: true
|
||||
client: true
|
||||
|
||||
user_preferences:
|
||||
default_email_digest_frequency:
|
||||
enum: "DigestEmailSiteSetting"
|
||||
|
|
|
@ -25,10 +25,6 @@ div.poll {
|
|||
}
|
||||
|
||||
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
|
||||
// from causing abrupt unintended scrolling
|
||||
&:not([width]):not(.emoji),
|
||||
|
|
Loading…
Reference in New Issue