Ну как-то так. Набросал на коленке.
.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, если они не динамически генерируются.