Как правильно сделать drag и scroll?
Все привет. Ребята, подскажите пожалуйста, как правильно сделать сделать следующий эффект:
https://chel.kassy.ru/event/2-8669/hall/ Слева есть div со span внутри. Перетаскивая его, мы скролим вверх и вниз некий выбранный контейнер. Как подобное реализовать. Может есть какие-то библиотеки. Либо же целесообразнее написать данный эффект самому? Важно, без использования jquery. |
Для решения этой задачи можно создать свой собственный элемент <element-map>, который показывает для некоторого элемента окошко, в котором указывается, в каком месте этот элемент виден, т. н. «навигатор».
Допустим, у нас есть элемент #app, внутри которого находится значительно бо́льшая по размеру картинка. <section id="app"> <img> </section> Элемент <element-map> может располагаться внутри #app, в таком случае <element-map> использует #app, как просматриваемую область. <section id="app"> <element-map></element-map> <img> </section> Но вы можете захотеть, чтобы «навигатор» насполагался вне просматриваемой области. В таком случае можно добавить атрибут for, который в качестве значения принимает id просматриваемой области. <section id="app"> <img> </section> <element-map for="app"></element-map> Также вы можете захотеть менять размеры «навигатора», но напрямую это делать нельзя, поскольку пропорции «навигатора» зависят от просматриваемой области. Но можно указать максимальный размер «навигатора» при помощи атрибута max-size. (Например max-size="200×200", т. е. «навигатор» будет принимать размеры, ограниченные площадью 200 на 200 пикселей) Вот реализация вышеописанного... <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> </head> <body> <script> class ElementMap extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ` <style> :host { display: block; position: absolute; margin: 8px; background-color: rgba(255, 255, 255, .5); background-size: cover; box-shadow: inset 0 0 1px black; border-radius: 5px; backdrop-filter: blur(5px); overflow: hidden; } :host[for] { position: relative; } .handle { border-radius: 5px; position: absolute; cursor: grab; box-shadow: inset 0 0 0 1px red, 0 0 0 100vmax rgba(0, 0, 0, 0.4); } </style> <div class="handle"></div> `; this.applyScrollPosition = this.applyScrollPosition.bind(this); this.pointerHandler = this.pointerHandler.bind(this); this.handle = this.shadowRoot.querySelector(".handle"); this.handle.addEventListener("pointerdown", this.pointerHandler); } attributeChangedCallback(name, oldValue, newValue) { if(name === "for" && newValue && this.ownerDocument) { this.forElement = this.ownerDocument.getElementById(newValue); } } static get observedAttributes() { return ["max-size", "for"]; } set forElement(element) { this._container = element; } get forElement() { if("_container" in this) return this._container; let element = null; if(this.hasAttribute("for")) { const id = this.getAttribute("for"); if(this.ownerDocument) element = this.ownerDocument.getElementById(id); } else element = this.parentNode; return this._container = element; } get maxSize() { if(this.hasAttribute("max-size")) { return this.getAttribute("max-size").trim().split(/\D/).map(Number); } else { return [150, 150]; } } connectedCallback() { this.forElement.style.overflow = "auto"; this.applyScrollPosition(); this.forElement.addEventListener("scroll", this.applyScrollPosition); this.ownerDocument.defaultView.addEventListener("load", this.applyScrollPosition); } disconnectedCallback() { this.forElement.removeEventListener("scroll", this.applyScrollPosition); this.forElement.ownerDocument.defaultView.removeEventListener("load", this.applyScrollPosition); } pointerHandler(event) { const { screenX, screenY } = event; if(event.type === "pointerdown") { document.addEventListener("pointermove", this.pointerHandler); document.addEventListener("pointerup", this.pointerHandler); this._startX = screenX; this._startY = screenY; this._scrollLeft = this.forElement.scrollLeft; this._scrollTop = this.forElement.scrollTop; } if(event.type === "pointerup") { document.removeEventListener("pointermove", this.pointerHandler); document.removeEventListener("pointerup", this.pointerHandler); } if(event.type === "pointermove") { const dx = (screenX - this._startX) / this.ρ; const dy = (screenY - this._startY) / this.ρ; this.forElement.scrollTo( this._scrollLeft + dx, this._scrollTop + dy ); } event.preventDefault(); } applyScrollPosition() { const { offsetWidth, offsetHeight, scrollWidth, scrollHeight, scrollLeft, scrollTop } = this.forElement; this.ρ = Math.min( this.maxSize[0] / scrollWidth, this.maxSize[1] / scrollHeight ); this.style.width = `${this.ρ * scrollWidth }px`; this.style.height = `${this.ρ * scrollHeight}px`; this.handle.style.cssText = ` width: ${this.ρ * offsetWidth }px; height: ${this.ρ * offsetHeight}px; left: ${this.ρ * scrollLeft }px; top: ${this.ρ * scrollTop }px; `; } } customElements.define("element-map", ElementMap); </script> <style>::-webkit-scrollbar{height:0;width:0}</style> <!-- Пример --> <section id="app" style="max-width: 600px; height: 400px; box-shadow: 0 0 1em black;"> <element-map style="background-image: url(http://lowbird.com/data/images/2018/03/structureoffreemasonryac3.jpg);"></element-map> <img src="http://lowbird.com/data/images/2018/03/structureoffreemasonryac3.jpg" alt="The Structure of Freemasonry" style="display: block;"> </section> <element-map for="app" max-size="200×200"></element-map> </body> </html> |
Malleys,
:thanks: |
Malleys,
можно пояснить, что делают строки 013 this.attachShadow({ mode: "open" }); и 057 static get observedAttributes() { return ["max-size", "for"]; } ? |
Огромное спасибо!
|
Цитата:
Цитата:
Например, если вы написали компонент, который хотите использовать на страницах, причем так, чтобы стили страниц никак не влияли на визуальное оформление компонента, то вы можете подключить теневой DOM. Без такой изоляции в DOM применяемые компоненты могут воздействовать друг на друга (например, применённые стили из других компонентов), вот теневой DOM позволяет использовать без оглядки на используемые на странице и во всех её фрагментах стили. Откройте настройки, например, Chrome DevTools (когда смотрите консоль, нажмите F1) и включите «Show user agent shadow DOM», теперь вы можете посмотреть как устроены, например, элементы <input type="range">, <select>, <video> и т. д. «изнутри». Так же нужно помнить, что наружу из компонента видны только те события, которые вы пробросите и никто не сможет подписаться на какой-нибудь "change" у <input> внутри компонента. Цитата:
|
Malleys,
спасибо ... ваш код для меня, отрытие чего-то нового |
Часовой пояс GMT +3, время: 19:03. |