各位大大好!我之前有做鳥類圖鑑是用display:flex+display:grid+fetch讀json檔的Responsive Filterable Image Gallery,我今天改寫成React function componenet+fetch讀json檔的Responsive Filterable Image Gallery,我編輯器是vscode+go livie,用React.js的cdn方式,但出現錯誤訊息和點左圖片後,右邊沒有出現圖片和文字說明,不知道是否用fetch讀json檔有問題,該怎麼辦呢?
google瀏覽器上的錯誤訊息:
Inline Babel script:311 Uncaught (in promise) TypeError: Cannot set properties of null (setting 'innerHTML')at :733:27
程式碼部分:
result1.innerHTML = `<img src=${bird.url} alt=${bird.name}/>`;
result2.innerHTML ="<h1>" + bird.name + "</h1>" + "<p>" + bird.category + "," + bird.local + "," + bird.description + "</p>";
google瀏覽器出現錯誤照片部分:
執行後,點左邊圖片,右邊沒出現圖片和文字敘述,照片部分:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="demo"></div>
<script type="text/babel">
let result1 = document.getElementById("divImg");
let result2 = document.getElementById("divShow");
function BirdGallery() {
let list = document.querySelectorAll('.list');
let itemBox = document.querySelectorAll('.itemBox');
for (let i = 0; i < list.length; i++) {
// ul 的 list 點擊監聽事件
list[i].addEventListener('click', function() {
for (let j = 0; j < list.length; j++) {
// 移除 .active 的 CSS
list[j].classList.remove('active');
}
// 每個 ul 的 list 新增 .active
this.classList.add('active');
// 取得 HTML 標籤上的 data-filter 屬性
let dataFilter = this.getAttribute('data-filter');
for (let k = 0; k < itemBox.length; k++) {
// 移除 .active 的 CSS
itemBox[k].classList.remove('active');
// 新增 .hide 的 CSS
itemBox[k].classList.add('hide');
// 判斷 div 的 data-item 是否等於 ul 的 dataFilter,或 dataFilter 等於 "all"
if (itemBox[k].getAttribute('data-item') == dataFilter || dataFilter == "all") {
// 移除 .hide 的 CSS
itemBox[k].classList.remove('hide');
// 新增 .active 的 CSS
itemBox[k].classList.add('active');
}
}
});
}
return (
<div>
<header id="birdheader">
<p>鳥類圖鑑</p>
</header>
<main>
<section>
<ul>
<li className="list active" data-filter="all">全部</li>
<li className="list" data-filter="local">本土鳥類</li>
<li className="list" data-filter="transit">過境鳥、候鳥</li>
<li className="list" data-filter="all" onClick={() => Clear()}>清除</li>
</ul>
</section>
<section className="birdsection">
<div className="bird">
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird1.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird2.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird3.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird4.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird5.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird6.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird7.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird8.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird9.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird10.jpg"/></a>
</div>
</div>
</div>
<div id="birdbox">
<div id="divImg"></div>
<div className="block">
<div id="divShow"></div>
<button className="birdbtn"><a href="#birdheader">回到最上面</a></button>
</div>
</div>
</section>
</main>
<footer>
<div className="ft">
© 版權所有 2023 THY 鳥類網站
</div>
</footer>
</div>
);
}
function show(ns) {
fetch("json/bird.json")
.then(function(response) {
return response.json(); // 選擇 JSON 格式
})
.then(function(data) {
// 使用迴圈讀取 JSON 檔案內容並將資料存放在 bird 變數中
for (let i = 0; i < data.length; i++) {
// 將 JSON 檔資料存放在 bird 中
let bird = data[i];
// 若鳥類名稱在 JSON 檔案中與傳遞過來的鳥類名稱相符,則顯示照片、鳥名和特徵;否則,不顯示內容
if (ns == bird.name) {
result1.innerHTML = `<img src=${bird.url} alt=${bird.name}/>`;
result2.innerHTML ="<h1>" + bird.name + "</h1>" + "<p>" + bird.category + "," + bird.local + "," + bird.description + "</p>";
}
}
});
}
// 外部定義 Clear 函式
function Clear() {
result1.innerHTML = "";
result2.innerHTML = "";
}
const root = ReactDOM.createRoot(document.getElementById('demo')).render(<BirdGallery/>);
</script>
</body>
</html>
style.css檔:
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@100&family=Lobster&family=Montserrat&family=Noto+Sans+TC:wght@300&family=Poppins&family=Roboto&display=swap');
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
header{
height:100px;
width: 100%;
background-color:black;
color: white;
text-align: center;
}
header p{
font-size:56px;
position: relative;
top:10%;
}
main{
margin: 10px auto;
}
section ul{
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-bottom: 20px;
}
section ul li{
list-style: none;
background: #eee;
padding: 12px 20px;
margin: 5px;
letter-spacing: 1px;
cursor: pointer;
}
section ul li.active{
background:black;
color: #fff;
}
.birdsection{
display:grid;
grid-template-columns: repeat(2,1fr);
}
.bird{
display:grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(90px, 1fr));
grid-row-gap:10px;
grid-column-gap:5px;
width: 100%;
height:515px;
overflow:scroll;
}
.bird::-webkit-scrollbar {
width: 10px;
height: 10px;
background-color:#F8F8FF;
}
.bird::-webkit-scrollbar-thumb{
background-color: #F8F8FF;
border: 2px solid #F5F5F5;
}
.bird .itemBox{
position: relative;
width: 90px;
height: 90px;
margin: 5px;
display: block;
}
.bird .itemBox.hide{
display: none;
}
/*
object-fit:cover
object-fit 屬性指定元素的內容應該如何去適應指定容器的高度與寬度
cover:保持原有尺寸比例。但部分内容可能被剪切。
*/
.bird .itemBox img{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
}
.bird .itemBox img:hover{
opacity: 0; /* 初始透明度為0,即完全透明 */
animation: fadeInOut 3s infinite; /* 使用 fadeInOut 動畫,3s 表示動畫持續時間 */
}
@keyframes fadeInOut {
0%, 100% {
opacity: 0; /* 開始和結束時完全透明 */
}
50% {
opacity: 1; /* 中間時完全不透明 */
}
}
#birdbox{
display:grid;
grid-template-columns: repeat(2,1fr);
}
#divImg img{
width:495px;
height:495px;
object-fit: cover;
}
#divImg img:hover{
transform-origin: center; /* 設置縮放的基準點為圖片的中心 */
animation: fadeInOut_1 3s infinite; /* 使用 zoomInOut 動畫,3s 表示動畫持續時間 */
}
@keyframes fadeInOut_1{
0%, 100% {
opacity: 0; /* 開始和結束時完全透明 */
}
50% {
opacity: 1; /* 中間時完全不透明 */
}
}
.itemBox img,#divImg img{
border-radius: 5%;
}
#divShow{
width:450px;
height:435px;
overflow: scroll;
overflow-wrap: break-word;
white-space: normal;
text-align: center;
margin-left:35px;
cursor: pointer;
}
#divShow:hover{
background-color: #eee;
}
#divShow::-webkit-scrollbar {
width: 10px;
height: 10px;
background-color:#F8F8FF;
}
#divShow::-webkit-scrollbar-thumb{
background-color: #F8F8FF;
border: 2px solid #F5F5F5;
}
#divShow h1{
font-size:32px;
text-align:center;
}
#divShow p{
font-size:20px;
}
.birdbtn{
width: 100px;
height:50px;
background-color:#F8F8FF;
border: 1px solid #F5F5F5;
border-radius: 10px;
position: relative;
left: 40%;
right: 42%;
margin-top: 10px;
cursor: pointer;
}
.birdbtn a{
text-decoration: none;
color: black;
}
.birdbtn:hover{
position: relative; /* 設置圖片為相對定位,以便進行平移 */
animation: zoomInOut 3s infinite; /* 使用 translateX 動畫,3s 表示動畫持續時間 */
}
@keyframes zoomInOut {
0%, 100% {
transform: scale(1); /* 開始和結束時不進行縮放,即正常大小 */
}
50% {
transform: scale(1.5); /* 中間時進行縮放,即放大到原來的1.5倍 */
}
}
footer{
height:100px;
width: 100%;
background-color:black;
color: white;
text-align: center;
}
footer .ft{
font-size:24px;
position: relative;
top: 35%;
}
@media screen and (max-width:415px){
header{
height:80px;
width: 100%;
}
header p{
font-size:48px;
position: relative;
top:5%;
}
.birdsection{
display: grid;
grid-template-columns: repeat(1,1fr);
}
.bird{
display:grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(200px, 1fr));
grid-row-gap:5px;
grid-column-gap:5px;
justify-items: center;
align-items: center;
width:100%;
height: 625px;
}
.bird .itemBox{
position: relative;
width: 200px;
height: 200px;
margin: 5px;
display: block;
}
#birdbox{
display: grid;
grid-template-columns: repeat(1,1fr);
}
#divImg img{
width:200px;
height:200px;
object-fit: cover;
}
#divShow{
width: 200px;
height: 185px;
}
#divShow h1{
font-size:24px;
}
#divShow p{
font-size:16px;
}
.birdbtn{
position: relative;
left: 25%;
right: 25%;
margin:5px;
}
footer{
height:80px;
width: 100%;
background-color:black;
color: white;
text-align: center;
}
footer .ft{
font-size:18px;
position: relative;
top: 32%;
}
}
@media screen and (max-width:290px){
header{
height:80px;
width: 100%;
}
header p{
font-size:48px;
position: relative;
top:5%;
}
#divShow h1{
font-size:18px;
}
#divShow p{
font-size:16px;
}
footer{
height:80px;
width: 100%;
background-color:black;
color: white;
text-align: center;
}
footer .ft{
font-size:18px;
position: relative;
top: 32%;
}
}
json檔:
[
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird1.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird2.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird3.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird4.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird5.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird6.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird7.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird8.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird9.jpg"
},
{
"name":"鳥名",
"category":"科別",
"local":"台灣本土鳥類",
"description":"敘述",
"url":"image/bird10.jpg"
}
]
React 還沒產生出網頁元素就用這兩句,造成 result1 跟 result2 都變成 null,後續要指派 result1.innerHTML 屬性的內容當然就出錯了
let result1 = document.getElementById("divImg");
let result2 = document.getElementById("divShow");
也許你把const root = ReactDOM.createRoot(document.getElementById('demo')).render(<BirdGallery/>);
放到 script 最前面就可以解決了?或是直到要指派 innerHTML 時才去取得 result1 跟 result2 也有機會解決這問題,不知道為何你要那麼早取得 result1 跟 result2?
let result1 = document.getElementById("divImg");
let result2 = document.getElementById("divShow");
你看看你這兩行寫在 BirdGallery 元件內,網頁上就能正常的呈現多個鳥類圖片
let list = document.querySelectorAll('.list');
let itemBox = document.querySelectorAll('.itemBox');
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird1.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird2.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird3.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird4.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird5.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird6.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird7.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird8.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')}><img src="image/bird9.jpg"/></a>
</div>
<div className="itemBox" data-item="local" >
<a href="#birdbox" onClick={()=>show('鳥名')} ><img src="image/bird10.jpg"/></a>
</div>