jupyter-book icon indicating copy to clipboard operation
jupyter-book copied to clipboard

Improve behaviour of clicked images in Jupyter Book (suggestion: Modal Image?)

Open firasm opened this issue 3 years ago • 13 comments

Is your feature request related to a problem? Please describe.

When users click an image in a jupyter book (regardless of how it is added), they are taken to a new page with just the image. There is no longer a sidebar (left, or TOC) and users have to click on the Back buttons to return to the content.

For an example, see this page for an example.

Current Behaviour after clicking an image:

Screen Shot 2020-09-01 at 11 11 39 PM

Describe the solution you'd like

Suggested improvement (requires extra CSS and JS):

Screen Shot 2020-09-01 at 11 12 56 PM

Describe alternatives you've considered

  • Doing nothing when images are clicked (not sure if this is better or worse?)

This is more of a discussion feature request to see if people have other ideas.

Additional context

Unfortunately (and of course) the solution doesn't actually work at the moment - to get it to do what I want, I ended up editing the HTML file after running jb build. This is super bad, but works in a pinch for this issue.

I got the code for this from w3schools.

firasm avatar Sep 02 '20 06:09 firasm

Yeh thanks, I guess this would be best implemented in a separate sphinx extension. I would note though that opening the image in its own page is very useful for mobile use, see https://github.com/executablebooks/meta/discussions/114

chrisjsewell avatar Sep 02 '20 06:09 chrisjsewell

Hmm interesting use case above.

For the record since you brought up mobile, here’s how it looks: 91942148-46d4bb00-ecaf-11ea-8755-8e38dfc59fb0

firasm avatar Sep 02 '20 07:09 firasm

FYI, you know you can include your own JS/CSS: https://jupyterbook.org/advanced/sphinx.html#custom-css-or-javascript

chrisjsewell avatar Sep 02 '20 08:09 chrisjsewell

Edited to post the code, rather than only link to my files

I have implemented this here if you would like to use. Style is here. I have lots of other style changes, so see below for just code of modal

Demo

custom.css:

/* The Modal (background) */
#wh-modal {
  display: none;
  /* Hidden by default */
  position: fixed;
  /* Stay in place */
  z-index: 9999;
  /* Sit on top */
  padding-top: 100px;
  /* Location of the box */
  left: 0;
  top: 0;
  width: 100%;
  /* Full width */
  height: 100%;
  /* Full height */
  overflow: auto;
  /* Enable scroll if needed */
  background-color: rgb(0, 0, 0);
  /* Fallback color */
  background-color: rgba(0, 0, 0, 0.9);
  /* Black w/ opacity */
}

/* Modal Content (Image) */
#wh-modal-img {
  margin: auto;
  display: block;
  max-width: min(1200px, 100%);
}


/* The Close Button */
#wh-modal-close {
  position: absolute;
  top: 15px;
  right: 35px;
  color: #f1f1f1;
  font-size: 40px;
  font-weight: bold;
}

#wh-modal-close:hover,
#wh-modal-close:focus {
  color: #bbb;
  text-decoration: none;
  cursor: pointer;
}

.wh-fig-a:hover {
  cursor: pointer;
}

.wh-venti-button {
  background-color: transparent;
  max-width: min(1200px, 100%);
  border: none;
}

custom.js:

function openModal(src, lastFocus) {
    let modal = document.querySelector('#wh-modal')
    modal.style.display = 'block'
    let modalContent = document.querySelector('#wh-modal-img')
    modalContent.src = src;
    let span = document.querySelector('#wh-modal-close')
    span.focus()
    modal.onclick = () => {
        modal.style.display = 'none'
        lastFocus.focus()
    }
    span.onclick = () => {
        modal.style.display = 'none'
        lastFocus.focus()
    }
}

function insertAnchors(element) {
    if (element.parentElement.tagName !== 'A') {
        const newButtonContainer = document.createElement('div')
        const newButton = document.createElement('button')
        newButtonContainer.appendChild(newButton)
        const p = element.parentElement
        element.parentElement.removeChild(element)
        newButton.appendChild(element)
        p.appendChild(newButtonContainer)
        newButton.onclick = () => openModal(element.getAttribute('src'), newButton)
        newButton.classList.add('wh-fig-a')
        newButton.classList.add('wh-venti-button')
        newButton.setAttribute('tabIndex', '0')
        newButtonContainer.classList.add('wh-flex-center')
    }
}

function addImgAnchors() {
    let figs = document.querySelectorAll('.figure img')
    figs.forEach(halfSize)
    figs.forEach(insertAnchors)
    let cellOutputs = document.querySelectorAll('.cell_output img')
    cellOutputs.forEach(halfSize)
    cellOutputs.forEach(insertAnchors)
}
window.addEventListener('load', addImgAnchors)

Also add the modal to your _config.yml

html:
 extra_footer              : <div id="wh-modal"> <button class="wh-venti-button" aria-label="close modal" id="wh-modal-close">✕</button> <img id="wh-modal-img"> </div>

whitead avatar Sep 02 '20 14:09 whitead

Thanks Andrew - that's exactly what I was looking for and similar to what I attempted myself (commit) but on build, the html was changed so it didn't work.

Anyway - I have tried to reproduce your solution on a blank book but I can't get it working (of course, cloning your repo works!)...

I tried copying the css and js files into the _static folder, and then deployed the book, but I have a feeling I'm missing something else.

Did you do anything special to coerce all the images in your notebooks to exhibit the modal behaviour?

In your book you don't have an example of a figure with just markdown, is there a class that needs to be added to the <img src... code to trigger the modal?

Repo where I tried this and the deployed version of the markdown file and the figure generated from a notebook

firasm avatar Sep 02 '20 18:09 firasm

This is counter-intuitive, but when I wrote this it was meant to only affect jupyter-notebook output which doesn't normally have a link. So on this line I check if the image already has a parent anchor and do not modify if already has a link. You can change this to always delete the anchor element and replace with modal. Just make sure to use the anchor's parent when adding the button.

whitead avatar Sep 02 '20 18:09 whitead

Clever!

Ah well, I can't even get it working for jupyter notebook outputs! I think there must be something else somewhere because if I copy my .ipynb file to your Jupyter Book, images show up in modals as expected.

But if I copy your .css and .js files to my _static folder, I get a javascript error...

Screen Shot 2020-09-02 at 3 48 54 PM

I even compared the generated .html files line by line and I don't see anything that could be causing the error!

at a loss...

firasm avatar Sep 02 '20 22:09 firasm

@firasm I'm so sorry, I forgot a key detail. You need to actually add the modal

 extra_footer              : <div id="wh-modal"> <button class="wh-venti-button" aria-label="close modal" id="wh-modal-close">✕</button> <img id="wh-modal-img"> </div>

Hope you didn't spend too long on that...

whitead avatar Sep 03 '20 00:09 whitead

aha!! That was it! No problem, and thank you - my students have you to thank for this!!

@chrisjsewell this is a sufficient workaround for me, so I'm happy to close this but if you think this should be supported natively (perhaps using a sphinx extension) we can leave it open or create a new issue to track it.

firasm avatar Sep 03 '20 00:09 firasm

It is even "worse" than this. If my image is produced by plt.show() then clicking on it does nothing.

The workaround didn't work properly for me, but I will put that in a separate response.

Edit. It seems that the output plt.show() should not be expected to have clickable behaviour.

I tried the workaround by @whitead but it did not work for me. His sample book looks great, but I could not reproduce it (though I didn't try building his book from the github source).

Here is what my output looks like:

Screenshot_2022-08-09_00-11-41

You can see the plot is half-size, which is not readable. Then when I click it:

Screenshot_2022-08-09_00-16-43

The axes and their labels are not showing.

Any ideas how to get @whitead 's method working?

Hi @quantitative-technologies - my previous post linked to my current master version where I have made some other style changes, including rendering output at half-size (so I can double the size of plots, enabling larger images on modal open). You also have a transparent plotface so you do not have a white background around your axes. Try:

import matplotlib as mpl
mpl.rcParams['figure.facecolor'] = '#FFFFFFFF'

I'll edit that post to have minimal code shortly.

whitead avatar Aug 08 '22 19:08 whitead

Thanks @whitead for updating the post! However, now it seems to do nothing.

No worries, the default click on image behaviour is good enough for my current project.

A completely separate issue: It seems from your demo that you were able to make the middle content column wider relative to the sidebars. I posted an issue on this because I couldn't figure out how to do it.