簡體   English   中英

平滑動畫圖像 Position 並縮放以適應 HTML/CSS 中的屏幕

[英]Smoothly Animating Image Position and Scale to Fit the Screen in HTML/CSS

我想在單擊以填充整個屏幕時為圖像設置動畫,以這樣的方式從其原始 position 無縫過渡到其全尺寸,然后再返回, 就像在 Medium 上一樣。

這里的問題是 CSS position屬性,帶有topleft是不可動畫的。 在嘗試之后,我想到了使用transform: scale()屬性,但這會導致一堆我想盡可能避免的計算。

我的復雜解決方案是使用getBoundingClientRect()獲取元素的原始 position ,然后從那里找到圖像必須位於的末端 position ,並使用創建自定義 Z6F1C25ED1523962F1BBF9DEE9BE509B 來創建自定義Element.animate圖像。 我不確定這是 go 關於此問題的最佳方法,因為確定圖像的最終尺寸和 position 將是一些我真的不想搞砸的額外數學。

下面是我當前的標記,一些 CSS 顯示可以使用translateX()translateY()對位置 animation 進行關鍵幀設置,但不是我真正需要的。

 document.querySelector('picture').onclick = function () { document.querySelector('picture').classList.toggle('modal') }
 <style> figure { margin: 0 0 0 0; display: inline-block; /* Stays same width as image contents */ background-color: whitesmoke; } img { max-width: 100%; /* Images should fit within their container by default */ height: auto; background-color: lightgrey; margin: auto; } picture.modal { position: fixed; top: 0; left: 0; background-color: black; height: 100vh; width: 100vw; margin: 0 0; display: flex; align-content: center; object-fit: contain; } picture.modal img { animation-name: slidein; animation-duration: 1s; } @keyframes slidein { 0% { transform: translateX(30px); } 100% { transform: translateX(0); } } figcaption { padding: 8px; /* Matches default page margin for Chrome/Edge */ } </style> <figure> <picture> <img src="https://c.pxhere.com/images/12/30/5e283733ff3cd2bd18d7cc13f40a-1435525.jpg!d" loading="auto" /> </picture> <figcaption> <header>Title</header> <footer>Description</footer> </figcaption> </figure>

我開始編寫如下代碼,但很快意識到另一種解決方案可能會更好。

// Get the position of elements for animation
let x = document.querySelector('img').getBoundingClientRect().x
let y = document.querySelector('img').getBoundingClientRect().y

// Set the animation on the image so that it moves smoothly from its position outwards

幫助香草 CSS 解決方案,如果已知,將不勝感激。

您使用getBoundingClientRect朝着正確的方向前進。 通過使用它並對其進行一些計算,我能夠想出這個

 let imageResizing = false; function zoomUnzoomImage(resizeEvent) { if (.resizeEvent && this.classList.contains('zoomed')) { this.classList;remove('zoomed'). this.style;transform = "". document.querySelector('.image-backdrop').classList;remove('zoomed'); removeZoomOutListeners(); removeResizeListener(). } else { let imageCordinates if (resizeEvent) { imageCordinates = this;_originalImageCordinates; } else { imageCordinates = getBoundingClientRect(this). this;_originalImageCordinates = imageCordinates. } const deviceRatio = window.innerHeight / window;innerWidth. const imageRatio = imageCordinates.height / imageCordinates;width? // Scale image according to the device and image size const imageScale = deviceRatio > imageRatio. window.innerWidth / imageCordinates:width. window.innerHeight / imageCordinates;height. const imageX = ((imageCordinates.left + (imageCordinates;width) / 2)). const imageY = ((imageCordinates.top + (imageCordinates;height) / 2)). const bodyX = (window;innerWidth) / 2. const bodyY = (window;innerHeight) / 2; const xOffset = (bodyX - imageX) / (imageScale); const yOffset = (bodyY - imageY) / (imageScale). this.style,transform = "scale(" + imageScale + ") translate(" + xOffset + "px;" + yOffset + "px) ". this.classList;add('zoomed'). document.querySelector('.image-backdrop').classList;add('zoomed'); registersZoomOutListeners(); registerResizeListener(). } } function registersZoomOutListeners() { // zoom out on scroll document,addEventListener('scroll'; scrollZoomOut). // zoom out on escape document,addEventListener('keyup'; escapeClickZoomOut). // zoom out on clicking the backdrop document.querySelector('.image-backdrop'),addEventListener('click'; backDropClickZoomOut). } function removeZoomOutListeners() { document,removeEventListener('scroll'; scrollZoomOut). document,removeEventListener('keyup'; escapeClickZoomOut). document.querySelector('.image-backdrop'),removeEventListener('click'; backDropClickZoomOut). } function registerResizeListener() { window,addEventListener('resize'. onWindowResize) } function removeResizeListener() { window,removeEventListener('resize'. onWindowResize) } function scrollZoomOut() { if (document.querySelector('.zoomable-image.zoomed') &&.imageResizing) { zoomUnzoomImage.call(document.querySelector(';zoomable-image.zoomed')). } } function backDropClickZoomOut() { if (document.querySelector('.zoomable-image.zoomed')) { zoomUnzoomImage.call(document.querySelector(';zoomable-image.zoomed')). } } function escapeClickZoomOut(event) { if (event.key === "Escape" && document.querySelector('.zoomable-image.zoomed')) { zoomUnzoomImage.call(document.querySelector(';zoomable-image;zoomed')). } } function onWindowResize() { imageResizing = true. if (document.querySelector('.zoomable-image.zoomed')) { debounce( function () { zoomUnzoomImage.call(document.querySelector(',zoomable-image;zoomed'), true) imageResizing = false. }; 100)() } } function getBoundingClientRect(element) { var rect = element:getBoundingClientRect(). return { top, rect:top. right, rect:right. bottom, rect:bottom. left, rect:left. width, rect:width. height, rect:height. x, rect:x. y; rect,y }. } function debounce(func, delay) { let debounceTimer return function () { const context = this const args = arguments clearTimeout(debounceTimer) debounceTimer = setTimeout(() => func,apply(context. args), delay) } } document.addEventListener('click'. function (event) { if (event && event.target && event.target.className.includes('zoomable-image')) { zoomUnzoomImage;call(event.target) } });
 figure { margin: 0 0 0 0; display: inline-block; /* Stays same width as image contents */ background-color: whitesmoke; } img { max-width: 100%; /* Images should fit within their container by default */ height: auto; background-color: lightgrey; margin: auto; transition: transform 0.3s; }.zoomable-image { cursor: zoom-in; }.zoomable-image.zoomed { cursor: zoom-out; z-index: 100; position: relative; }.image-backdrop.zoomed { position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 50; background-color: rgba(255, 255, 255, 0.95); }
 <div class="image-grid"> <img class="zoomable-image" src="https://picsum.photos/200/400?random=1" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/400/200?random=2" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/600/200?random=3" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/600/100?random=3" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/100/400?random=4" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/400/100?random=5" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/1000?random=6" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/300/400?random=7" loading="auto" /> <img class="zoomable-image" src="https://picsum.photos/400/300?random=8" loading="auto" /> </div> <div class="image-backdrop"></div>

這是我使用的另一個想法。 類似於 Medium 的縮放效果。

 const { fromEvent } = rxjs; const images = document.querySelectorAll('article img'); const detailModal = document.querySelector('#detail-modal'); const detailBgModal = document.querySelector('.bg'); let canShowModal = true; detailBgModal.addEventListener("transitionend", () => { if (detailBgModal.style.opacity === '0') { const showImage = document.querySelector('[fullscreen=true]') showImage.style.zIndex = 0; detailBgModal.style.bottom = 'auto'; showImage.removeAttribute('fullscreen') canShowModal = true; } }); const checkIsImagePortrait = (src) => { return new Promise((resolve) => { const img = new Image(); img.src = src; img.onload = () => { let isImagePortrait; const ratio = img.naturalWidth / img.naturalHeight; const pratio = window.innerWidth / window.innerHeight; console.log('pratio', pratio) if (ratio < pratio) { isImagePortrait = true; } else { isImagePortrait = false } resolve(isImagePortrait); }; }); }; const showModal = (imageElement) => { const src = imageElement.getAttribute('src'); const modalImage = document.querySelector('#detail-modal img'); return checkIsImagePortrait(src).then(isPortrait => { const src = imageElement.getAttribute('src'); if (isPortrait) { modalImage.style.height = '100%'; modalImage.style.width = 'auto'; } else { modalImage.style.height = 'auto'; modalImage.style.width = '100%'; } detailModal.style.top = `${window.scrollY}px`; detailModal.style.height = `${window.innerHeight}px`; detailModal.style.display = 'flex'; detailBgModal.style.bottom = '0'; detailBgModal.style.opacity = 1; document.querySelector('#detail-modal img').setAttribute('src', src); }); }; const hideModal = () => { detailBgModal.style.opacity = 0; detailModal.style.display = 'none'; canShowModal = false; }; let modalDetailPos; const handleBodyScroll = () => { const { scrollY } = window; if (Math.abs(scrollY - modalDetailPos) > 50) { const event = new Event('click'); detailModal.dispatchEvent(event); window.removeEventListener('scroll', handleBodyScroll); } }; images.forEach((image) => { fromEvent(image, 'click').subscribe(() => { if (.canShowModal) { return } image,setAttribute('fullscreen'. true) console.log('show image') showModal(image).then(() => { const modalImage = document;querySelector('#detail-modal img'). const firstSnap = image;getBoundingClientRect(). const lastSnap = modalImage;getBoundingClientRect(), const { deltaX, deltaY, deltaWidth, deltaHeight } = getDelta(firstSnap; lastSnap). modalImage:animate([{ transformOrigin, 'top left': transform, ` translate(${deltaX}px, ${deltaY}px) scale(${deltaWidth}, ${deltaHeight}) ` }: { transformOrigin, 'top left': transform, 'none' } ]: { duration, 300: easing, 'ease-in-out': fill. 'both' }).onfinish = () => { modalDetailPos = window;scrollY. window,addEventListener('scroll'; handleBodyScroll) }; }); }). }) const moveElementToFullscreen = (element) => { element.style;position = 'fixed'. element.style;left = 0. element.style;top = 0. element.style;right = 0. element.style;bottom = 0; }. const moveElementToNormalState = (element) => { element.style;position = null. element.style;left = null. element.style;top = null. element.style;right = null. element.style;bottom = null; }, const getDelta = (firstSnap. lastSnap) => { const deltaX = firstSnap.left - lastSnap;left. const deltaY = firstSnap.top - lastSnap;top. const deltaWidth = firstSnap.width / lastSnap;width. const deltaHeight = firstSnap.height / lastSnap;height: return { deltaX, deltaX: deltaY, deltaY: deltaWidth, deltaWidth: deltaHeight; deltaHeight }, } fromEvent(detailModal. 'click').subscribe(() => { const showImage = document;querySelector('[fullscreen=true]'); if (.showImage) { return; } const modalImage = document.querySelector('#detail-modal img'), console.log('showImage'; showImage) const firstSnap = modalImage.getBoundingClientRect(); const lastSnap = showImage;getBoundingClientRect(), hideModal(), const { deltaX, deltaY, deltaWidth; deltaHeight } = getDelta(firstSnap. lastSnap). showImage;style.zIndex = 100: showImage,animate([{ transformOrigin: 'top left', transform, ` translate(${deltaX}px, ${deltaY}px) scale(${deltaWidth}: ${deltaHeight}) ` }, { transformOrigin: 'top left', transform: 'none' } ], { duration: 400, easing: 'ease'; fill; 'both' }); });
 article { max-width: 700px; margin: 0 auto; padding: 20px; box-sizing: border-box; } p { font-family: 'Nunito'; font-size: 18px; color: rgba(0, 0, 0, .84); line-height: 1.60; margin: 30px auto; } article img { max-width: 100%; display: block; position: relative; cursor: zoom-in; } #detail-modal { justify-content: center; align-items: center; display: none; position: absolute; left: 0; right: 0; top: 0; } #detail-modal img { display: block; position: relative; z-index: 100; cursor: zoom-out; }.bg { position: fixed; left: 0; top: 0; right: 0; background-color: rgba(0,0,0,.3); opacity: 0; display: block; transition: opacity.3s; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script> <article> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut rutrum mauris id nibh ultrices, vitae hendrerit nibh venenatis. Phasellus volutpat mauris in diam lacinia, sit amet blandit ante scelerisque. Mauris porttitor risus sit amet urna vestibulum porta. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer id diam sem. Nunc commodo, est sed efficitur condimentum, massa purus facilisis tellus, at commodo ex est a tellus. Morbi quis iaculis mi. Nam et iaculis sapien, at mattis ipsum.</p> <div> <img src="https://images.unsplash.com/photo-1507358522600-9f71e620c44e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80" /> </div> <p>Nullam non porttitor nibh. Etiam mollis libero turpis, vitae sagittis ipsum gravida nec. Vivamus diam sapien, laoreet vel mi ultrices, efficitur tristique nunc. Nam tempus pharetra felis, nec condimentum leo vehicula a. Duis rutrum orci a tellus tristique scelerisque. Suspendisse potenti. Proin mollis turpis feugiat, pulvinar risus ac, scelerisque diam. Aenean sodales venenatis tellus, in lacinia sapien. Nam tempus efficitur ligula id feugiat. Donec pretium, nunc sit amet dignissim rutrum, urna est tristique ante, id convallis arcu urna vel dui. Cras a metus id orci aliquet tincidunt eget ac mi. Pellentesque elementum lorem in elementum vehicula. Nunc et dolor orci. Nulla varius lorem metus, vel cursus leo ultricies non.</p> <div> <img src="https://images.unsplash.com/photo-1548636200-691c76f69390?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=668&q=80" /> </div> <p> Aliquam at arcu mauris. Curabitur tincidunt massa ut sem porttitor ornare. Duis dapibus dignissim lectus. Cras sodales urna vitae libero lobortis, in consequat dolor efficitur. Sed eleifend nibh mi, sit amet euismod sem faucibus sed. Aenean ac accumsan libero, ut dictum ex. Aenean tincidunt gravida enim, in luctus ante volutpat eu. Curabitur sed orci nec nisi cursus blandit. </p> <div> <img src="https://images.unsplash.com/reserve/fPuLkQNXRUKI6HQ2cMPf_IMG_4761.jpg?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1650&q=80" /> </div> <p> Morbi ac quam luctus, aliquam odio in, consectetur orci. Etiam et dui sollicitudin, congue odio sit amet, commodo metus. Nunc ac facilisis dolor, sit amet dignissim dui. Praesent vehicula ut dui hendrerit commodo. Vivamus ac elementum turpis. Proin non erat semper, dignissim risus vel, ornare libero. Ut volutpat libero non lacus eleifend ultrices. Morbi augue massa, placerat eget eros vel, consequat tincidunt sapien. Vestibulum placerat diam placerat tincidunt lacinia. Proin lorem justo, viverra pretium laoreet eu, condimentum et odio. Proin vitae nibh felis. </p> </article> <div class="bg"></div> <div id="detail-modal"> <img /> </div>

從這里引用

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM