(CSS) How to Style the First Image in a Note?

Things I have tried

I’ve tried using img:first-of-type to select the first image of a note, but it still selects every image on the page.

What I’m trying to do

I’m trying to make a css snippet that will make the first image in a note a sort of “banner image” that floats to the top of the page and spans the whole width of the note. Preferably with the first header spanning over it. Essentially, I’m trying to copy this:

.
So, I know it IS possible in Obsidian. I just haven’t found a way to do it yet. Please help me.

2 Likes

I would guess that what you see in that screenshot was done with the Banners Plugin by Danny Hernandez.

2 Likes

The person in this thread claimed they achieved it with CSS. It does look a lot like the plug-in, though.

I’ve used the banner plug-in before, but I wanted to see if there was a solution that would automatically make the first image in the note a banner.

Ah ok, too bad he didn’t post the css back then. You could also try the Obsidian Discord Channel, I think there is a lobby named #csssnippets or something. Lots of CSS gurus there :wink:

1 Like

Okay, I’ll check it out. Thanks for the help!

If someone happens to have the answer, it would be great if you could post the solution here.

1 Like

You could add a class to your note or template’s front-matter:

e.g.

---
cssclass: custom-banner
---

Then add a custom CSS snippet with that class defined. The CSS styles needed could be extracted from the Banner Plugin’s style sheet located here:

2 Likes

img:first-of-type alone will not work, since the images are not direct siblings but each in their own tree of nested divs. Actually, this is good news, since this makes it possible to have a banner for multiple open tabs.

Since just one level higher up the parent divs of imgs are siblings, I thought that something like .cm-content div:has(img):first-of-type img should do the trick. Unfortunately, it does not. I still post it, so maybe somebody can take it up and make it work…

1 Like

So far I’ve managed to create a banner image that adapts to the note width using @gapmiss’ advice. To use it, you type obsidian-banner in the frontmatter. This is the code:

 .obsidian-banner img {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 300px;
  width: 100%;
  margin-right: auto;
  margin-left: auto;
  object-fit: cover;
  object-position: center;
  overflow: hidden;
  user-select: none;
 }
 .obsidian-banner .inline-title {
  margin-top: 300px;
 }

 .obsidian-banner.is-readable-line-width img {
  width: var(--file-line-width);
}

This is what it looks like:

However, with this code the banner uses the last image in the note, not the first one. I still can’t get it to select the first image.

i made few tweaks from your previous example. i must say, make it work in Live Preview is a bit tricky because using position: absolute will throw off editing experience of your note. Luckily I remember a trick I used with display: contents. Anyway, here’s what I managed…

  • use image alt “banner” i.e. ![[imagefile.jpg|banner]] as a way to target a specific image for banner. can use first image by targeting the first one after yaml, but i feel this image alt gives better flexibility
  • make the image banner in live preview not affecting editing experience. position: absolute will make the first few top lines almost impossible to select
  • added option to adjust vertical positioning (albeit just 3 options in total – center (default), higher (20% higher using image alt “higher”), and lower (20% lower, image alt “lower”
  • make the note title slightly overlap the banner. need to use position: relative and z-index: 1 to make the title on top of the banner.
    • I tried to explore gradient filter that makes bottom of the picture gradually whiter (or any other color) but to no success

the full css snippet with a number of tweaks

/* banner using css only -- https://forum.obsidian.md/t/css-how-to-style-the-first-image-in-a-note/52839/ */

    /* make the div (in LP) containing the image doesn't control the css box-sizing */
    .internal-embed.image-embed[alt*="banner"] { display: contents; }
    /* original forum post snippet with minor tweak */
    .obsidian-banner img[alt*="banner"] {
        position: absolute;
        top: 0;
        left: 0;
        height: 200px;
        width: 100%;
        margin-right: auto;
        margin-left: auto;
        object-fit: cover;
        object-position: 50% 50%;
        overflow: hidden;
        user-select: none;
     }
     /* add option to adjust vertical position by adding the alt metadata "higher" or "lower" */
     .obsidian-banner img[alt*="banner"][alt*="lower"] { object-position: 50% 30%; }
     .obsidian-banner img[alt*="banner"][alt*="higher"] { object-position: 50% 70%; }

     /* original forum post with minor tweak to make it overlap */
     .obsidian-banner .inline-title {
         position: relative;
         padding-top: 150px;
         z-index: 1;
     }

     /* minor tweak for me, since i have snippet to make image has rounded corner */
     .obsidian-banner.is-readable-line-width img { border-radius: revert; }
     /* minor tweak for me, to hide snw block reference counter for the banner */
    .internal-embed.media-embed.image-embed.is-loaded + .snw-reference.snw-embed {display: none;}

screenshot on editing view and reading view

4 Likes

Wow! I would have never figured out how to do this on my own. Thanks for sharing. I also found a way to create a gradient that fades into the note.

.obsidian-banner img[alt*="banner"] {
-webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
mask-image: linear-gradient(to bottom, rgba(0,0,0,1), rgba(0,0,0,0));
}

I think you guys have a much better idea with choosing the banner image using the alt. This actually solves my problem. Thanks for your help!

I’m still curious about selecting that first image, though.

1 Like

superb! this will go into my boilerplate/reference notes. my knowledge on gradient so far has been limited to background-color. this definitely will come handy in the future.

anyway, i tried with targeting the first image. but i couldn’t make it work for editing view/live preview. it works just fine for reading view. either there’s another hidden first image or codemirror just behave differently (coz when i choose the last image, it selected the second last in my note).

so the best bet right now is using the image alt there. with that u can place the image practically anywhere in the note and it won’t disrupt any flow i can think of right now. plus u might want the custom control to adjust the position (my example using [[|higher]] to make a notch higher.

1 Like

Perhaps the CSS has() pseudo-class could be used to target the img without using the alt tag. With Obsidian’s recent upgrade to Electron v21, has() is now available. I may experiment with it but am curious if someone else(more adept) might take a crack at it.

Sibling Scopes in CSS, thanks to has()

Using has() as a CSS Parent Selector and much more

1 Like

There is some trickery to be done, which could potentially target the first image (without doing the alternate text trick, which is a far better solution). For the reading view it looks something like this:

div.markdown-preview-section div:has(p > span >img) {
  background-color: yellow;
} 

div.markdown-preview-section div:has(p > span > img) ~ div:has(p > span > img) {
  background-color: blue;
}

Now the first image will have a background-color of yellow, whilst the other have a blue background. In other words, if you want to set something for the first image, you need to revert that setting for all other images.

Of course, in the live preview, the CSS is different, and a lot more messy, so it’s not as easy to find selectors which can be considered siblings which make that trick useful in the reading view. You could target all images using something like .cm-contentContainer :has(div.cm-line > dv.image-embed, div.image-embed) div.image-embed, but this selector doesn’t co-operate with the sibling selector, ~, since the selector targets elements on different levels.

Another option, could be to use the :nth-child(n of <selector>) selector, but this is not supported in chromium until version 111, and we’re currently at ver 106 for Obsidian.

2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.