Files
preland/components/examples.php
T
Panfilov Konstantin 45e20ed0cd
deploy-preland / deploy barbariskatest (push) Skipped
deploy-preland / deploy zoomstage.online (push) Successful in 19s
deploy-preland / deploy zoompixel.online (push) Skipped
deploy-preland / deploy zoompixel.ru (push) Skipped
fix: align stage preland picker and demo layout
2026-10-17 07:39:37 +03:00

315 lines
12 KiB
PHP

<?php
$demoDirName = 'demo' . ($indpic === '1' ? '' : '-' . $indpic);
$demoBasePath = rtrim($assetsUriPrefix, '/') . "/images/{$demoDirName}";
$demoLocalPath = __DIR__ . "/../images/{$demoDirName}";
$getAssetUrl = function (string $relativePath) use ($demoBasePath, $demoLocalPath): string {
$fullPath = $demoLocalPath . '/' . ltrim($relativePath, '/');
$version = file_exists($fullPath) ? filemtime($fullPath) : '1';
return $demoBasePath . '/' . ltrim($relativePath, '/') . '?v=' . $version;
};
$labelBefore = Yii::t('app', 'До');
$labelAfter = Yii::t('app', 'После');
$sourceMob = $getAssetUrl('1/source_mob.avif');
$sourceOrig = $getAssetUrl('1/source.avif');
$resultMob = $getAssetUrl('1/result_mob.avif');
$resultOrig = $getAssetUrl('1/result.avif');
?>
<div id="examples">
<div class="image-compare" id="image-compare">
<img src="<?= $sourceMob ?>"
srcset="<?= $sourceMob ?> 768w, <?= $sourceOrig ?> 1696w"
sizes="(max-width: 767.98px) 100vw, 728px"
id="image-compare-source"
fetchpriority="high"
loading="eager"
width="728"
height="395"
alt="<?= htmlspecialchars($labelBefore) ?>">
<img src="<?= $resultMob ?>"
srcset="<?= $resultMob ?> 768w, <?= $resultOrig ?> 1696w"
sizes="(max-width: 767.98px) 100vw, 728px"
id="image-compare-result"
fetchpriority="high"
loading="eager"
width="728"
height="395"
alt="<?= htmlspecialchars($labelAfter) ?>">
</div>
<div class="image-examples">
<div class="image-examples-label"><?= Yii::t('app', 'Примеры улучшения') ?></div>
<div class="image-examples-list">
<?php for ($i = 1; $i <= 5; $i++):
$thumbMob = $getAssetUrl("{$i}/thumb_mob.avif");
$thumbOrig = $getAssetUrl("{$i}/thumb.avif");
?>
<div class="image-examples-item <?= $i === 1 ? 'active' : '' ?>"
data-index="<?= $i ?>"
data-source="<?= $getAssetUrl("{$i}/source.avif") ?>"
data-result="<?= $getAssetUrl("{$i}/result.avif") ?>"
data-source-mob="<?= $getAssetUrl("{$i}/source_mob.avif") ?>"
data-result-mob="<?= $getAssetUrl("{$i}/result_mob.avif") ?>">
<div class="image-examples-image">
<?php if ($i === 1): ?>
<picture>
<source media="(max-width: 767.98px)" srcset="<?= $thumbMob ?>">
<img src="<?= $thumbOrig ?>" width="53" height="53" alt="Example 1">
</picture>
<?php else: ?>
<picture class="deferred-picture">
<source media="(max-width: 767.98px)" data-srcset="<?= $thumbMob ?>">
<img data-src="<?= $thumbOrig ?>" width="53" height="53" alt="Example <?= $i ?>" class="deferred-thumb">
</picture>
<?php endif; ?>
</div>
</div>
<?php endfor; ?>
</div>
</div>
</div>
<script>
const examples = {
el: document.getElementById('examples'),
titleEl: document.getElementById('form-title'),
hide() {
window.scrollTo(0, 0);
window.scrollTo({ top: 0, behavior: 'instant' });
if (this.el) this.el.style.display = 'none';
},
show() {
if (this.el) this.el.style.display = '';
},
setTitle(html) {
if (this.titleEl) this.titleEl.innerHTML = html;
}
};
function setupNativeCompare(element, labelBefore, labelAfter) {
if (!element || element.classList.contains('native-icv') || element.classList.contains('icv')) return null;
const sourceImg = element.querySelector('#image-compare-source');
const resultImg = element.querySelector('#image-compare-result');
if (!sourceImg || !resultImg) return null;
element.classList.add('native-icv');
const beforeWrap = document.createElement('div');
beforeWrap.className = 'icv__before-wrap';
resultImg.className = 'icv__after-img';
const handle = document.createElement('div');
handle.className = 'icv__handle';
const circle = document.createElement('div');
circle.className = 'icv__handle-circle';
handle.appendChild(circle);
const lblBefore = document.createElement('div');
lblBefore.className = 'icv__label icv__label-before';
lblBefore.textContent = labelBefore;
const lblAfter = document.createElement('div');
lblAfter.className = 'icv__label icv__label-after';
lblAfter.textContent = labelAfter;
beforeWrap.appendChild(sourceImg);
element.appendChild(resultImg);
element.appendChild(beforeWrap);
element.appendChild(handle);
element.appendChild(lblBefore);
element.appendChild(lblAfter);
function updateSlider(percent) {
percent = Math.max(0, Math.min(100, percent));
beforeWrap.style.width = percent + '%';
handle.style.left = percent + '%';
sourceImg.style.width = element.clientWidth + 'px';
}
window.addEventListener('resize', () => {
sourceImg.style.width = element.clientWidth + 'px';
});
let isDragging = false;
function getPos(e) {
const rect = element.getBoundingClientRect();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
return ((clientX - rect.left) / rect.width) * 100;
}
function onStart(e) {
isDragging = true;
updateSlider(getPos(e));
}
function onMove(e) {
if (!isDragging) return;
updateSlider(getPos(e));
}
function onEnd() {
isDragging = false;
}
element.addEventListener('mousedown', onStart);
element.addEventListener('touchstart', onStart, { passive: true });
window.addEventListener('mousemove', onMove);
window.addEventListener('touchmove', onMove, { passive: true });
window.addEventListener('mouseup', onEnd);
window.addEventListener('touchend', onEnd);
updateSlider(50);
return {
setImages(sourceUrl, resultUrl) {
sourceImg.removeAttribute('srcset');
resultImg.removeAttribute('srcset');
sourceImg.src = sourceUrl;
resultImg.src = resultUrl;
}
};
}
function initCompareViewer() {
const element = document.getElementById('image-compare');
const beforeTxt = '<?= addslashes($labelBefore) ?>';
const afterTxt = '<?= addslashes($labelAfter) ?>';
if (element && typeof ImageCompare !== 'undefined') {
const options = {
controlColor: "#FFFFFF",
controlShadow: true,
addCircle: true,
addCircleBlur: true,
showLabels: true,
labelOptions: { before: beforeTxt, after: afterTxt, onHover: false },
smoothing: true,
smoothingAmount: 100,
hoverStart: false,
verticalMode: false,
startingPoint: 50,
fluidMode: false
};
window.icvInstance = new ImageCompare(element, options).mount();
} else if (element) {
window.nativeCompareInstance = setupNativeCompare(element, beforeTxt, afterTxt);
}
const exampleItems = document.querySelectorAll('.image-examples-item');
exampleItems.forEach(item => {
item.onclick = function() {
const isMobile = window.innerWidth < 768;
// На мобильном берем source_mob, на ПК — полный оригинал
const source = (isMobile && this.getAttribute('data-source-mob'))
? this.getAttribute('data-source-mob')
: this.getAttribute('data-source');
const result = (isMobile && this.getAttribute('data-result-mob'))
? this.getAttribute('data-result-mob')
: this.getAttribute('data-result');
if (window.icvInstance) {
window.icvInstance.images.start.element.removeAttribute('srcset');
window.icvInstance.images.end.element.removeAttribute('srcset');
window.icvInstance.images.start.element.src = source;
window.icvInstance.images.end.element.src = result;
} else if (window.nativeCompareInstance) {
window.nativeCompareInstance.setImages(source, result);
} else {
const sourceEl = document.getElementById('image-compare-source');
const resultEl = document.getElementById('image-compare-result');
if (sourceEl) {
sourceEl.removeAttribute('srcset');
sourceEl.src = source;
}
if (resultEl) {
resultEl.removeAttribute('srcset');
resultEl.src = result;
}
}
exampleItems.forEach(el => el.classList.remove('active'));
this.classList.add('active');
};
});
}
function initStrictImagePipeline() {
initCompareViewer();
const preloadUrl = (url) => {
return new Promise(resolve => {
const img = new Image();
img.onload = img.onerror = resolve;
img.src = url;
});
};
const deferredPics = document.querySelectorAll('.deferred-picture');
const thumbPromises = [];
deferredPics.forEach(pic => {
const source = pic.querySelector('source');
const img = pic.querySelector('img');
if (source && source.getAttribute('data-srcset')) {
source.srcset = source.getAttribute('data-srcset');
}
if (img && img.getAttribute('data-src')) {
const p = new Promise(resolve => {
img.onload = img.onerror = resolve;
img.src = img.getAttribute('data-src');
});
thumbPromises.push(p);
}
});
Promise.all(thumbPromises).then(() => {
const queue = [2, 3, 4, 5];
async function loadSequentially() {
const isMobile = window.innerWidth < 768;
for (const num of queue) {
const item = document.querySelector(`.image-examples-item[data-index="${num}"]`);
if (!item) continue;
const s = (isMobile && item.getAttribute('data-source-mob'))
? item.getAttribute('data-source-mob')
: item.getAttribute('data-source');
const r = (isMobile && item.getAttribute('data-result-mob'))
? item.getAttribute('data-result-mob')
: item.getAttribute('data-result');
await Promise.all([preloadUrl(s), preloadUrl(r)]);
}
}
if ('requestIdleCallback' in window) {
requestIdleCallback(() => loadSequentially());
} else {
setTimeout(loadSequentially, 150);
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initStrictImagePipeline);
} else {
initStrictImagePipeline();
}
</script>