Skip to content

Commit c4e26ab

Browse files
authored
Prevent inherited styles on next/image wrapper or sizer (#30064)
As a follow up to #30041 which changed `<div>` to `<span>`, this PR makes sure that unexpected styles are not applied to the image wrapper or sizer spans. For example: `.content span {}` would apply to all spans and incorrectly style the image wrapper. This PR adds [`all: initial`](https://developer.mozilla.org/en-US/docs/Web/CSS/all) to effectively reset the span styles.
1 parent f308b8a commit c4e26ab

File tree

4 files changed

+127
-47
lines changed

4 files changed

+127
-47
lines changed

packages/next/client/image.tsx

Lines changed: 33 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,19 @@ export default function Image({
458458
})
459459
const isVisible = !isLazy || isIntersected
460460

461-
let wrapperStyle: JSX.IntrinsicElements['span']['style'] | undefined
462-
let sizerStyle: JSX.IntrinsicElements['span']['style'] | undefined
461+
const wrapperStyle: JSX.IntrinsicElements['span']['style'] = {
462+
all: 'initial',
463+
boxSizing: 'border-box',
464+
overflow: 'hidden',
465+
}
466+
const sizerStyle: JSX.IntrinsicElements['span']['style'] = {
467+
all: 'initial',
468+
boxSizing: 'border-box',
469+
display: 'block',
470+
}
471+
let hasSizer = false
463472
let sizerSvg: string | undefined
464-
let imgStyle: ImgElementStyle | undefined = {
473+
const imgStyle: ImgElementStyle = {
465474
position: 'absolute',
466475
top: 0,
467476
left: 0,
@@ -495,19 +504,12 @@ export default function Image({
495504
: {}
496505
if (layout === 'fill') {
497506
// <Image src="i.png" layout="fill" />
498-
wrapperStyle = {
499-
display: 'block',
500-
overflow: 'hidden',
501-
502-
position: 'absolute',
503-
top: 0,
504-
left: 0,
505-
bottom: 0,
506-
right: 0,
507-
508-
boxSizing: 'border-box',
509-
margin: 0,
510-
}
507+
wrapperStyle.display = 'block'
508+
wrapperStyle.position = 'absolute'
509+
wrapperStyle.top = 0
510+
wrapperStyle.left = 0
511+
wrapperStyle.bottom = 0
512+
wrapperStyle.right = 0
511513
} else if (
512514
typeof widthInt !== 'undefined' &&
513515
typeof heightInt !== 'undefined'
@@ -517,41 +519,24 @@ export default function Image({
517519
const paddingTop = isNaN(quotient) ? '100%' : `${quotient * 100}%`
518520
if (layout === 'responsive') {
519521
// <Image src="i.png" width="100" height="100" layout="responsive" />
520-
wrapperStyle = {
521-
display: 'block',
522-
overflow: 'hidden',
523-
position: 'relative',
524-
525-
boxSizing: 'border-box',
526-
margin: 0,
527-
}
528-
sizerStyle = { display: 'block', boxSizing: 'border-box', paddingTop }
522+
wrapperStyle.display = 'block'
523+
wrapperStyle.position = 'relative'
524+
hasSizer = true
525+
sizerStyle.paddingTop = paddingTop
529526
} else if (layout === 'intrinsic') {
530527
// <Image src="i.png" width="100" height="100" layout="intrinsic" />
531-
wrapperStyle = {
532-
display: 'inline-block',
533-
maxWidth: '100%',
534-
overflow: 'hidden',
535-
position: 'relative',
536-
boxSizing: 'border-box',
537-
margin: 0,
538-
}
539-
sizerStyle = {
540-
display: 'block',
541-
boxSizing: 'border-box',
542-
maxWidth: '100%',
543-
}
528+
wrapperStyle.display = 'inline-block'
529+
wrapperStyle.position = 'relative'
530+
wrapperStyle.maxWidth = '100%'
531+
hasSizer = true
532+
sizerStyle.maxWidth = '100%'
544533
sizerSvg = `<svg width="${widthInt}" height="${heightInt}" xmlns="http://www.w3.org/2000/svg" version="1.1"/>`
545534
} else if (layout === 'fixed') {
546535
// <Image src="i.png" width="100" height="100" layout="fixed" />
547-
wrapperStyle = {
548-
display: 'inline-block',
549-
overflow: 'hidden',
550-
boxSizing: 'border-box',
551-
position: 'relative',
552-
width: widthInt,
553-
height: heightInt,
554-
}
536+
wrapperStyle.display = 'inline-block'
537+
wrapperStyle.position = 'relative'
538+
wrapperStyle.width = widthInt
539+
wrapperStyle.height = heightInt
555540
}
556541
} else {
557542
// <Image src="i.png" />
@@ -584,11 +569,12 @@ export default function Image({
584569

585570
return (
586571
<span style={wrapperStyle}>
587-
{sizerStyle ? (
572+
{hasSizer ? (
588573
<span style={sizerStyle}>
589574
{sizerSvg ? (
590575
<img
591576
style={{
577+
all: 'initial',
592578
maxWidth: '100%',
593579
display: 'block',
594580
margin: 0,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react'
2+
import Image from 'next/image'
3+
import style from '../style.module.css'
4+
5+
const Page = () => {
6+
return (
7+
<div id="main-container" className={style.mainContainer}>
8+
<h1>Image Style Inheritance</h1>
9+
<Image
10+
id="img-fixed"
11+
layout="fixed"
12+
src="/test.jpg"
13+
width="400"
14+
height="400"
15+
/>
16+
17+
<Image
18+
id="img-intrinsic"
19+
layout="intrinsic"
20+
src="/test.jpg"
21+
width="400"
22+
height="400"
23+
/>
24+
25+
<div style={{ position: 'relative', width: '200px', height: '200px' }}>
26+
<Image id="img-fill" layout="fill" src="/test.jpg" objectFit="cover" />
27+
</div>
28+
29+
<Image
30+
id="img-responsive"
31+
layout="responsive"
32+
src="/test.jpg"
33+
width="400"
34+
height="400"
35+
/>
36+
37+
<footer>Footer</footer>
38+
</div>
39+
)
40+
}
41+
42+
export default Page
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
.displayFlex {
22
display: flex;
33
}
4+
5+
.mainContainer {
6+
border-radius: 75px;
7+
}
8+
9+
.mainContainer span {
10+
border-radius: 50px;
11+
}
12+
13+
.mainContainer img {
14+
border-radius: 100px;
15+
}

test/integration/image-component/default/test/index.test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,46 @@ function runTests(mode) {
699699
}
700700
})
701701

702+
it('should apply style inheritance for img elements but not wrapper elements', async () => {
703+
let browser
704+
try {
705+
browser = await webdriver(appPort, '/style-inheritance')
706+
707+
await browser.eval(
708+
`document.querySelector("footer").scrollIntoView({behavior: "smooth"})`
709+
)
710+
711+
const allImgs = await browser.eval(`
712+
function foo() {
713+
const imgs = document.querySelectorAll("img[id]");
714+
for (let img of imgs) {
715+
const br = window.getComputedStyle(img).getPropertyValue("border-radius");
716+
if (!br) return 'no-border-radius';
717+
if (br !== '100px') return br;
718+
}
719+
return true;
720+
}()
721+
`)
722+
expect(allImgs).toBe(true)
723+
724+
const allSpans = await browser.eval(`
725+
function foo() {
726+
const spans = document.querySelectorAll("span");
727+
for (let span of spans) {
728+
const br = window.getComputedStyle(span).getPropertyValue("border-radius");
729+
if (br && br !== '0px') return br;
730+
}
731+
return false;
732+
}()
733+
`)
734+
expect(allSpans).toBe(false)
735+
} finally {
736+
if (browser) {
737+
await browser.close()
738+
}
739+
}
740+
})
741+
702742
// Tests that use the `unsized` attribute:
703743
if (mode !== 'dev') {
704744
it('should correctly rotate image', async () => {

0 commit comments

Comments
 (0)