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

Responsive images for the slideshow? #13

Open
robots4life opened this issue Feb 26, 2020 · 12 comments
Open

Responsive images for the slideshow? #13

robots4life opened this issue Feb 26, 2020 · 12 comments

Comments

@robots4life
Copy link

Hi Arno,

First of all thank you for your plugin. I really like it, am an avid follower of photoswipe, using it in its pure form a lot. Now I am working on a WordPress site and am trying to get responsive images going for the slideshow.

I use the following responsive picture syntax.
It is the Changing image sizes, high-DPI images & different image types use case from here.
https://dev.opera.com/articles/responsive-images/
(Note this is not about art direction, where completely different images are served based on viewport.)

<picture>
	<source
		sizes="(min-width: 640px) 60vw, 100vw"
		srcset="opera-200.webp 200w,
				opera-400.webp 400w,
				opera-800.webp 800w,
				opera-1200.webp 1200w,
				opera-1600.webp 1600w,
				opera-2000.webp 2000w"
		type="image/webp">
	<img
		src="opera-400.jpg" alt="The Oslo Opera House"
		sizes="(min-width: 640px) 60vw, 100vw"
		srcset="opera-200.jpg 200w,
				opera-400.jpg 400w,
				opera-800.jpg 800w,
				opera-1200.jpg 1200w,
				opera-1600.jpg 1600w,
				opera-2000.jpg 2000w">
</picture>

This serves webp format images to browsers that support it and falls back to jpg format images for older browsers.

In fact I also combine this with lazysizes.
The whole thing then looks something like this.

    <picture
      ><source
        sizes="(min-width: 2700px) 1603px, (min-width: 1040px) calc(61.16vw - 36px), calc(100vw - 130px)"
        data-srcset="image_19-2560x3327.jpg 2560w, image_19-2048x2661.jpg 2048w, image_19-1920x2495.jpg 1920w, image_19-1600x2079.jpg 1600w, image_19-1440x1871.jpg 1440w, image_19-1280x1663.jpg 1280w, image_19-1140x1481.jpg 1140w, image_19-1024x1331.jpg 1024w, image_19-960x1248.jpg 960w, image_19-900x1170.jpg 900w, image_19-800x1040.jpg 800w, image_19-768x998.jpg 768w, image_19-640x832.jpg 640w, image_19-525x682.jpg 525w, image_19-425x552.jpg 425w, image_19-320x416.jpg 320w, image_19-240x312.jpg 240w, image_19-180x234.jpg 180w, image_19-120x156.jpg 120w"
        type="image/jpg"/>
      <noscript><img src="image_19-2560x3327.jpg"/></noscript
      ><img
        width="2560"
        height="3327"
        style="max-width: 2560px; max-height: 3327px;"
        class="lazyload"
        src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
        data-src="image_19-425x552.jpg"
        alt=""
        sizes="(min-width: 2700px) 1603px, (min-width: 1040px) calc(61.16vw - 36px), calc(100vw - 130px)"
        data-srcset="image_19-2560x3327.jpg 2560w, image_19-2048x2661.jpg 2048w, image_19-1920x2495.jpg 1920w, image_19-1600x2079.jpg 1600w, image_19-1440x1871.jpg 1440w, image_19-1280x1663.jpg 1280w, image_19-1140x1481.jpg 1140w, image_19-1024x1331.jpg 1024w, image_19-960x1248.jpg 960w, image_19-900x1170.jpg 900w, image_19-800x1040.jpg 800w, image_19-768x998.jpg 768w, image_19-640x832.jpg 640w, image_19-525x682.jpg 525w, image_19-425x552.jpg 425w, image_19-320x416.jpg 320w, image_19-240x312.jpg 240w, image_19-180x234.jpg 180w, image_19-120x156.jpg 120w"
    /></picture>

You might be curious about having the width and height attributes in there, well this is because of this upcoming feature. It helps with reducing page jank.

Regardless of all this, when I wrap this picture element into a link so that it can be shown through your excellent lightbox-photoswipe plugin I have to use the biggest source of that image, because if I don't, and the user is on a moderately large desktop they will either see a too small and blurry or a too large and blurry image in the slideshow. This is because the image dimensions need to be specified with the data attributes. For desktop this is not an issue. But..

This of course leads to loading huge images to users on mobile devices. So while the responsive picture element syntax takes care of that with having all the possible src and srcset attribute values, this is a bit trickier to accomplish when I like to use responsive images in the slideshow.

Short, how do you get responsive images for the slideshow going with your plugin? I can see in the photoswipe documentation that the native picture element and the srcset attribute are not supported. And they don't need to be supported. The picture element is merely the thumbnail that then opens the slideshow. On a large screen this thumbnail has a large size and on mobile it is small.

I guess all this leads to the following question. How could I generate or populate this slide object with the already existing sizes and then feed those to your plugin? More than happy to help you write code, frontend or backend, for this. What do you think?

@arnowelzel
Copy link
Owner

The problem here is, that PhotoSwipe does not use the picture which is displayed in the page but the one which is linked.

Example:

<a href="someimage.jpg"><img src="someimage-thumbnail.jpg" ...></a>

In this case the image used for the lightbox is someimage.jpg regardless what the <img> element contains. The idea is that the visible image is of course not the full size image but only a smaller thumbnail and the full size image is the one which is referred in the <a> element.

Of course it is possible for the frontend script to check if the <img> element has a data-srcset attribute and only use the image which fits the current viewport - but then the images in the lightbox would never be larger than the viewport and "pinch to zoom" would not work at all. I'm not sure if this is really what people expect, when they open an image in a lightbox. But technically - yes, I could add support for this as an option which could be enabled in the backend settings.

Detecting WebP might be possible as well as mentioned on https://stackoverflow.com/questions/5573096/detecting-webp-support:

function canUseWebP() {
    var elem = document.createElement('canvas');

    if (!!(elem.getContext && elem.getContext('2d'))) {
        // was able or not to get WebP representation
        return elem.toDataURL('image/webp').indexOf('data:image/webp') == 0;
    }

    // very old browser like IE 8, canvas not supported
    return false;
}

@robots4life
Copy link
Author

robots4life commented Mar 4, 2020

Using a class that creates the markup for the responsive picture element I managed to create a slide object as mentioned here. The slide object is created from the image array that WordPress holds, reading from the data-srcset attribute. In the slide object I can assign all the srcset values I want and then feed that to Photoswipe directly.

Regarding your plugin, what would be useful, would be a field where one could pass the WordPress image data array and then your JS creates the responsive slide object from that. Yes - shifting the array of srcset to 1 or 2 sizes above the current viewport, i.e. smallImage, mediumImage, originalImage - and then passing that in the slide object - would keep zoom enabled while also serving responsive images in the slideshow. What did you have in mind?

Could even try to read from the picture element, as you only need the srcset values. Exploring further how your plugin works. Is there a specific reason you chose jQuery for your js?

Yes - on the webp support as well, thank you!

@arnowelzel
Copy link
Owner

Thanks for the pointer to the documentation. I'll have a look into that and see how I can integrate this to my plugin.

About jQuery - well, it is shipped in WordPress anyway, so was is no need to avoid it and this part in the frontend is much easier with jQuery:

$('body').on('click', 'a[data-width]:has(img)', function(e) {
        if(!PhotoSwipe || !PhotoSwipeUI_Default) {
            return;
        }

        e.preventDefault();
        openPhotoSwipe(false, 0, this, false, '');
    });

Eventhough it is possible to do this with plain JavaScript as well using document.querySelectorAll() etc., it would require at least some additional code.

I did some quick tests: on my server the version of jQuery shipped with WordPress needs about 35 KB transfer volume (about 100 KB minified JS compressed using gzip) and adds no download time at all since thanks to HTTP/2 everything is downloaded in parallel. So the additional overhead is already not very big.

Maybe I remove jQuery as dependency in a future version, so if no other active plugin/theme needs it, it won't get used at all.

@robots4life
Copy link
Author

robots4life commented Mar 14, 2020

I might open another issue with your kind permission where we can discuss the JS/jQuery bits. I say, no script is as good as no script. Though I like your comparison and wonder about the point you make with "this part" being easier.

Regardless, to keep on topic here, I got the following going.
https://codepen.io/crazyrobot/pen/ZEGxOyM

The photoswipe_data array is printed to the Frontend.

Like this I
a) have slideshows independent of the page markup
b) hence dot not generate the slide object data from the markup
c) can, but do not have to show every slide
c) can have multiple slideshows per page or post with minimal JS
d) have each slideshow open on click of the corresponding button per data attribute

The data attribute name of the button is the key to the gallery_id value that the photoswipe_data array holds. Like this you can find the corresponding values by that key. this.gallery_id is the value that you get from the HTML, in the button data attribute, so in this codepen ID_1 or ID_2. With .find you can go over the photoswipe_data array and find the values for that key.

    this.slide = photoswipe_data.find(
      element => element.gallery_id === this.gallery_id
    )

So you can have multiple galleries and feed them the slide objects that you previously generated.

What I found notable, when doing responsive images, the title property, for the image caption, has to be outside the mediumImage or originalImage objects, so in an outer object.

Now about generating that JSON data with the slide_objects, title and so on. It was a bit tricky to get the data structure needed right be able to do a json_encode on the array and have PhotoSwipe be happy with it. But it does work, and it works well. At the moment I am just working with an ACF image field image array and generating the data from that.

Like this I give the user the option to just
a) show an image, do nothing
b) show a single image, mark it as slide, have it open the single instance of PhotoSwipe with its corresponding slide object
c) show multiple images, mark them as slides, pick one to show in the Frontend, have the rest just be slide object JSON data if wanted like that
d) finally show multiple groups of such slide objects, meaning, have multiple slideshows per page/post, if needed/wanted at all.

Including a gallery_id field in the image array made it then possible to group the slide objects.

@arnowelzel
Copy link
Owner

Just a quick reply about your points:

a) have slideshows independent of the page markup

This is not possible in WordPress. WordPress only provides the final markup and no structured information about the content. My plugin must use the markup since not all images come from galleries and not all gallery plugins have hooks to get the gallery data before the frontend markup is created. For example Gutenberg has a gallery block as well, but there is no hook for get attributes like a single caption for the whole gallery (see https://wordpress-demo.arnowelzel.de/lightbox-with-single-caption-for-multiple-images/ and https://wordpress-demo.arnowelzel.de/lightbox-with-photoswipe/).

b) hence dot not generate the slide object data from the markup

At least the backend must parse the markup anyway - as explained above.

However at the moment the backend only uses a simple regex to find images inside links in the markup to add the required attributes for the frontend.

The frontend is then using the DOM to build a list of all images for PhotoSwipe based on the attributes from the backend and to identify the captions in the markup (which can be quite tricky as for example with the common caption in Gutenberg galleries). If this would be done in the backend too, it would create a lot of additional load in the backend - and it's not needed since the browser has the DOM anyway and JavaScript can handle that quite well.

c) can, but do not have to show every slide

This could be controlled with additional attributes in the image, similar to the gallery ID which already groups images to be shown within a lightbox loop if needed.

However - from the end users perspective: how to add this information in the backend editor? As long as you just use TinyMCE you may switch to HTML view and add the required attributes in the markup manually. But with Gutenberg image blocks? Maybe there is an option to extend Gutenberg blocks to include additional information for other plugins, but I'm not sure if I'm willing to spend time on this.

c) can have multiple slideshows per page or post with minimal JS

There are already mulitple slideshows using the gallery ID attribute which is set by the backend if the user wants to have each gallery in its own slideshow. In the frontend this only requires two lines of code for a different selector if a users clicks an image which is part of a gallery which has a ID different to 1 (which indicates "this is part of a gallery").

So what is your point?

d) have each slideshow open on click of the corresponding button per data attribute

Hmm - I don't get your point again.

I have to deal with existing link elements in the markup which need additional event handlers attached, so clicking the images will open the lightbox.

And the part which adds the event handler to every link element which maches the selector a[data-width]:has(img) is not the only thing done using jQuery. parseThumbnailElements() also uses jQuery selectors. One thing I will change: the selector a[data-width] should be enough.

Of course this all can be done with plain JavaScript as well - it's just a bit more code.

@arnowelzel
Copy link
Owner

arnowelzel commented Mar 15, 2020

About removing jQuery:

I created a branch which provides a version without jQuery. See here: https://github.com/arnowelzel/lightbox-photoswipe/blob/no-jquery/js/frontend.js

Without jQuery a lot of additional checks are needed so the script gets nearly twice as big and this is just a "proof of concept" yet. There may still be bugs especially for getting the captions from the DOM - and as I already explained, there is no other way to do this.

@robots4life
Copy link
Author

Yeah no my post was never about things I am missing from your plugin, sorry if it comes across like that. I played around a lot with the plugin and then focused on the slide object data and figured, since I am in control for this particular use case, I could get the data and work with that. That worked out fine, so I posted the approach. Getting data from Backend vs collecting data from Frontend really.

And surly, while working on that, it crossed my mind to make a plugin, but then it hit me, like you say, there are no hooks for this, to get the image data needed, one cannot know what image should be in a slideshow or not and what its source is, unless all this is coded directly into the theme/template. Yes, Gutenberg is even heavier in that aspect. Totally get your point here.

To come to that.

My point in c) and d) was that with the data coming from the Backend I have a few lines of JS in the Frontend and one or multiple slideshows working. I guess I just mentioned this as I like to keep things down to only what is really needed, for the particular case, of course.

Also, yes, having the gallery id coming from the data and getting that object by getting the gallery id from the data attribute and using it as key to find the right object in the data makes it possible that either a link, button or a thumbnail can be used to open the slide or slideshow or slideshows. Again, all this is in a context of where one can generate the slide objects, in the context of a plugin this of course is different.

Great you made a JS version! Thank you for your work. Again, yes, I see what you mean with not having control of what will be shown and hence having to react upon that in the plugin context.

@arnowelzel
Copy link
Owner

arnowelzel commented Apr 2, 2020

The JS version does not work properly, so I reverted back to jQuery. I won't go back to pure JS very soon since I can't test the plugin with hundrets of themes and plugins which might cause a pure JS version to break.

Also see:
https://wordpress.org/support/topic/plugin-stopped-working-after-update-22/

@svaneke
Copy link

svaneke commented May 16, 2020

I am not a developer and my knowledge of code is limited. But Arno just help me with an issue related to the Visual Portfolio plugin from the Wordpress repository. It uses Photoswipe as well and it works with responsive images (example).

I just scrolled through the plugin’s class-images.php. Maybe there is some code to get inspired from?

@arnowelzel
Copy link
Owner

JFTR: Lightbox with PhotosSwipe not using jQuery any longer after some fixes in 2.94

@robots4life
Copy link
Author

Interesting development. When you posted that this is much easier with jQuery I did not really see what you meant.

$('body').on('click', 'a[data-width]:has(img)', function(e) {
        if(!PhotoSwipe || !PhotoSwipeUI_Default) {
            return;
        }

        e.preventDefault();
        openPhotoSwipe(false, 0, this, false, '');
    });

But now it seems you solved this with using vanilla JS. Nice one! I am intrigued. From a coding perspective, what are/were the main pain points for you mentioning that this above is better done with JS? Trying to learn and understand, not a critique. Or to put it simple, what lines of your current JS code would replace above lines of the formerly used jQuery code and what made you take the decision that vanilla JS would be the right way to go forward?

Concerning being more "free" from WordPress while still being able to provide PhotoSwipe. All the points you mentioned are right about WP. And this is sad, kind of. There a no hooks or functions that can, before a post is rendered, create an object of all post data, including images. In your case you can only react to rendered markup. You would have lot more options if you step in before that. If there would be hooks, etc., one could pull from the image data from that and create the gallery JSON data.

I solved it by creating custom fields and creating the gallery in the backend/dashbaord like that, using ACF. But using the Gutenberg editor, this is out of question. Well perhaps with ACF Blocks, but have not tried that. I find this is more a WP design issue rather than anything to do with your plugin.

Just trying to discuss really and see what clever ways there would be to "work around" WP. In fact, often when working with WP and wanting to do something a bit more fancier, well I find myself working "around" WP instead of "with" WP, I am sure you know what I mean.

Did you at any point have an other ideas to how this could be done? Integrating logic into each post editor and post data through a plugin that would be able to "know" what image sources are being used in the post. Then giving the user options what to do with those images concerning PhotoSwipe. Then working with already rendered source and reacting to that would be out of the way. But yeah, WP might not like that approach.

@arnowelzel
Copy link
Owner

arnowelzel commented May 17, 2020

About jQuery:

jQuery was less to type and therefore there are fewer chances for errors. For example - this is the code with jQuery to get an event handler attached to each a element which contains the attribute data-lbwps-width as indicator for a link to an image:

$('body').on('click', 'a[data-lbwps-width]', function(event) {
        if(!PhotoSwipe || !PhotoSwipeUI_Default) {
            return;
        }

        event.preventDefault();
        openPhotoSwipe(false, 0, this, false, '');
    });

And this is the same in vanilla JS - and just the minimal version assuming that all browsers will understand that without any graceful degradation for older browsers:

    var links = document.querySelectorAll('a[data-lbwps-width]');
    links.forEach(function(link) {
        link.addEventListener('click', function(event) {
            if (!PhotoSwipe || !PhotoSwipeUI_Default) {
                return;
            }

            event.preventDefault();
            openPhotoSwipe(false, 0, this, false, '');
        });
    });

A good thing about jQuery besides the fact that you write less code is the better compatibility. You don't have to think about how to implement something which runs on every major browser. jQuery will usually deal with browser specific differences.

On the other hand most browsers behave more or less the same since most of them are based either on Chromium or Gecko. Even Microsoft decided to use Chromium for their "Edge" browser. So dealing with the quirks of Internet Explorer is no longer neccessary. Therefore using vanilla JS is the better way nowadays.

And about better ways to integrate Photoswipe:

There are already page builders, galleries and other extensions for WordPress which use the official version of Photoswipe as well like Elementor, Visual Composer or WooCommerce (for product images). Maybe it would be a good idea to bring all these projects together so they won't only use their own version of Photoswipe but allow to use the version from my WP plugin. I already had a number of support requests where people asked me how to disable the old version of Photoswipe in a theme or gallery plugin, so they can use the version from my plugin.

A problem is however - some of these projects are commercial ones and they don't like to rely on a free open source project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants