Javascript.RU

Создать новую тему Ответ
 
Опции темы Искать в теме
  #1 (permalink)  
Старый 16.03.2019, 18:59
Аспирант
Отправить личное сообщение для gsdev99 Посмотреть профиль Найти все сообщения от gsdev99
 
Регистрация: 03.02.2019
Сообщений: 72

Как правильно сделать drag и scroll?
Все привет. Ребята, подскажите пожалуйста, как правильно сделать сделать следующий эффект:
https://chel.kassy.ru/event/2-8669/hall/
Слева есть div со span внутри. Перетаскивая его, мы скролим вверх и вниз некий выбранный контейнер.
Как подобное реализовать. Может есть какие-то библиотеки. Либо же целесообразнее написать данный эффект самому?
Важно, без использования jquery.
Ответить с цитированием
  #2 (permalink)  
Старый 17.03.2019, 04:07
Аватар для Malleys
Профессор
Отправить личное сообщение для Malleys Посмотреть профиль Найти все сообщения от Malleys
 
Регистрация: 20.12.2009
Сообщений: 1,714

Для решения этой задачи можно создать свой собственный элемент <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>
Ответить с цитированием
  #3 (permalink)  
Старый 17.03.2019, 08:23
Аватар для рони
Профессор
Отправить личное сообщение для рони Посмотреть профиль Найти все сообщения от рони
 
Регистрация: 27.05.2010
Сообщений: 33,064

Malleys,
Ответить с цитированием
  #4 (permalink)  
Старый 17.03.2019, 08:30
Аватар для рони
Профессор
Отправить личное сообщение для рони Посмотреть профиль Найти все сообщения от рони
 
Регистрация: 27.05.2010
Сообщений: 33,064

Malleys,
можно пояснить, что делают строки
013
this.attachShadow({ mode: "open" });

и
057
static get observedAttributes() { 
        return ["max-size", "for"]; 
    }

?
Ответить с цитированием
  #5 (permalink)  
Старый 17.03.2019, 16:21
Аспирант
Отправить личное сообщение для gsdev99 Посмотреть профиль Найти все сообщения от gsdev99
 
Регистрация: 03.02.2019
Сообщений: 72

Огромное спасибо!
Ответить с цитированием
  #6 (permalink)  
Старый 17.03.2019, 19:04
Аватар для Malleys
Профессор
Отправить личное сообщение для Malleys Посмотреть профиль Найти все сообщения от Malleys
 
Регистрация: 20.12.2009
Сообщений: 1,714

Сообщение от рони
можно пояснить, что делают строки
Сообщение от рони
this.attachShadow({ mode: "open" });
Подключает к элементу теневой DOM, который изолирован от остального DOM.

Например, если вы написали компонент, который хотите использовать на страницах, причем так, чтобы стили страниц никак не влияли на визуальное оформление компонента, то вы можете подключить теневой DOM.

Без такой изоляции в DOM применяемые компоненты могут воздействовать друг на друга (например, применённые стили из других компонентов), вот теневой DOM позволяет использовать без оглядки на используемые на странице и во всех её фрагментах стили.

Откройте настройки, например, Chrome DevTools (когда смотрите консоль, нажмите F1) и включите «Show user agent shadow DOM», теперь вы можете посмотреть как устроены, например, элементы <input type="range">, <select>, <video> и т. д. «изнутри». Так же нужно помнить, что наружу из компонента видны только те события, которые вы пробросите и никто не сможет подписаться на какой-нибудь "change" у <input> внутри компонента.

Сообщение от рони
static get observedAttributes() {
    return ["max-size", "for"];
}
Изменение каких атрибутов отслеживать, когда что-то из указанного поменяется, вызовется метод attributeChangedCallback

Последний раз редактировалось Malleys, 17.03.2019 в 19:07.
Ответить с цитированием
  #7 (permalink)  
Старый 17.03.2019, 19:24
Аватар для рони
Профессор
Отправить личное сообщение для рони Посмотреть профиль Найти все сообщения от рони
 
Регистрация: 27.05.2010
Сообщений: 33,064

Malleys,
спасибо ... ваш код для меня, отрытие чего-то нового
Ответить с цитированием
Ответ



Опции темы Искать в теме
Искать в теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Как правильно реализовать следующую логику на событии scroll? s24344 Элементы интерфейса 2 03.02.2019 12:00
Как правильно организовать подключение скриптов? s24344 Общие вопросы Javascript 0 31.12.2018 11:53
Как правильно сделать следующую верстку? s24344 (X)HTML/CSS 2 05.11.2018 12:37
Как правильно реализовать такой функционал? Julian Общие вопросы Javascript 3 16.01.2015 12:34
Как правильно обновить div из БД в MVC??? espltd AJAX и COMET 2 11.04.2014 01:28