Ну как-то так. Набросал на коленке.
.pic {
display: inline-block;
}
.pic img {
opacity: 1;
transition: opacity 1s linear;
}
.pic img.invisible {
opacity: 0;
}
button {
padding: 1em;
}
button.active {
background: tomato;
}
document.body.addEventListener('click', e => {
if (!e.target.matches('button')) return
const img = document.querySelector('.pic img')
img.classList.add('invisible')
img.ontransitionend = () => {
img.onload = () => img.classList.remove('invisible')
img.src = e.target.dataset.src
document.querySelectorAll('button').forEach(btn => btn.classList.remove('active'))
e.target.classList.add('active')
}
})
P.s. недостатком данного кода является то, что загрузка следующей картинки начинается только после того, как первая исчезла. Это можно оптимизировать, но не всёж за Вас решать :-)
А ещё не обязательно каждый раз искать элементы в DOM, если они не динамически генерируются.