Журнал Тильды
/
Библиотека промптов
/
Изображение с эффектом рентгена
Изображение с эффектом рентгена
Две фотографии наложены друг на друга: под курсором появляется мягкое круглое окно, сквозь которое видно нижний снимок. Так можно показать, что скрыто внутри предмета, или сравнить два состояния.
Промпт
Вы можете скопировать промпт и вставить его в вайб блок в поле ввода. Подробнее в инструкции.
Создай интерактивный блок X-ray reveal: при движении курсора мягкое круглое окно в верхней фотографии открывает нижнюю. Без увеличения изображения, заголовков, текста, кнопок и автопрокрутки.

Весь контент должен легко редактироваться в Тильде: обе фотографии сразу пропиши отдельными img в HTML с data-tilda-img="y". Не создавай контент через JS, массивы, innerHTML или canvas. JS управляет только эффектом через CSS-переменные. В редакторе отключи анимацию и покажи оба исходных фото доступными для выбора и замены, без перехвата кликов. На опубликованной странице наложи их друг на друга.

Нижнее фото, открываемое внутри окна:
https://static.tildacdn.com/tild6362-6565-4561-b234-643363396136/Group_2136139225.jpg
Верхнее фото, видимое в покое:
https://static.tildacdn.com/tild3930-6636-4139-b861-326439333564/1_12.jpg

Белая секция width:100%, padding:72px 20px. По центру кадр width:min(92vw,1180px), aspect-ratio:16/10, radius:4px, overflow:hidden, фон #f2f2f2, isolation:isolate. Оба img занимают кадр целиком: position:absolute; inset:0; width/height:100%; object-fit:cover; object-position:center; draggable:false. Нижнее z-index:1, обёртка верхнего z-index:2, контур окна z-index:3.

В покое радиус r=0, видна только верхняя фотография. При входе мыши в кадр сразу поставь центр окна в точку входа, затем плавно увеличь r до 150px. При движении центр следует за указателем с инерцией; при выходе r плавно уменьшается до 0. Обычный курсор внутри кадра скрыт.

Прозрачное отверстие сделай CSS mask-image и -webkit-mask-image на обёртке верхнего img: radial-gradient(ellipse calc(r*sx*2) calc(r*sy*2) at x y, transparent 0%, transparent 54%, rgba(0,0,0,.25) 74%, #000 100%). Центр полностью раскрывает нижнее фото, края мягко смешивают снимки. Не используй жёсткую круговую обрезку. Поверх — тонкое кольцо размером 2r×2r, border:1px solid rgba(255,255,255,.72), radius:50%, тень 0 0 0 1px rgba(0,0,0,.10), pointer-events:none; центр совпадает с окном.

Анимация через requestAnimationFrame: lerp позиции .20, радиуса .13; поправка на время кадра factor=1-(1-alpha)^(delta/16.67), delta≤100ms. Лёгкая упругость: speed=hypot(dx,dy)/delta, stretchTarget=clamp(speed*.004,0,.42), сглаживание .20; sx=1+stretch, sy=1-stretch*.42. Кольцо translate(-50%,-50%) rotate(angle) scaleX(sx) scaleY(sy); angle=atan2(dy,dx) при speed>.35. Маска использует sx/sy без вращения.

768–1199px: padding:56px 16px, кадр 3:2. ≤767px: padding:32px 14px, width:calc(100vw - 28px), кадр 4:5, обычный курсор. На touch окно открывается под пальцем до r=132px, следует за касанием и закрывается при отпускании/cancel. Координаты ограничь кадром; passive-обработчики не блокируют прокрутку страницы.

Изолируй CSS/JS, поддержи несколько экземпляров без повторной инициализации. ResizeObserver пересчитывает размер, вне экрана и в скрытой вкладке rAF остановлен. При reduced-motion отключи эффект и кольцо, покажи верхнее фото с обычным курсором.
Скопировать промпт
Код
Вы можете скопировать код и вставить его в вайб блок в раздел код. Подробнее в инструкции.
<section class="vbx-root" data-vbx-reveal="" aria-label="X-ray image reveal">

<div class="vbx-frame" aria-label="Image reveal">

<img class="vbx-photo vbx-photo--bottom" data-tilda-img="y" src="https://static.tildacdn.com/tild6362-6565-4561-b234-643363396136/Group_2136139225.jpg" alt="" draggable="false">

<div class="vbx-top-wrap">
<img class="vbx-photo vbx-photo--top" data-tilda-img="y" src="https://static.tildacdn.com/tild3930-6636-4139-b861-326439333564/1_12.jpg" alt="" draggable="false">
</div>

<div class="vbx-ring" aria-hidden="true"></div>

</div>

<style>
.vbx-root,
.vbx-root * {
box-sizing: border-box;
}

.vbx-root {
--vbx-x: 50%;
--vbx-y: 50%;
--vbx-r: 0px;
--vbx-rx: 1;
--vbx-ry: 1;
--vbx-angle: 0deg;

display: block;

width: 100%;

padding:
72px 20px;

background:
#ffffff;

font-family:
Arial,
Helvetica,
sans-serif;

text-align: center;
}

.vbx-frame {
position: relative;

display: inline-block;

width:
min(92vw,1180px);

aspect-ratio:
16 / 10;

overflow: hidden;

border-radius:
4px;

background:
#f2f2f2;

cursor: none;

vertical-align: top;

isolation: isolate;
}

.vbx-photo {
position: absolute;

inset: 0;

display: block;

width: 100%;
height: 100%;

object-fit: cover;
object-position: center;

user-select: none;
-webkit-user-drag: none;
}

.vbx-photo--bottom {
z-index: 1;

pointer-events: auto;
}

.vbx-top-wrap {
position: absolute;

inset: 0;

z-index: 2;

overflow: hidden;

pointer-events: none;

-webkit-mask-image:
radial-gradient(
ellipse
calc(var(--vbx-r) * var(--vbx-rx) * 2)
calc(var(--vbx-r) * var(--vbx-ry) * 2)
at var(--vbx-x) var(--vbx-y),
transparent 0%,
transparent 54%,
rgba(0,0,0,0.25) 74%,
#000 100%
);

mask-image:
radial-gradient(
ellipse
calc(var(--vbx-r) * var(--vbx-rx) * 2)
calc(var(--vbx-r) * var(--vbx-ry) * 2)
at var(--vbx-x) var(--vbx-y),
transparent 0%,
transparent 54%,
rgba(0,0,0,0.25) 74%,
#000 100%
);
}

.vbx-photo--top {
z-index: 1;

pointer-events: auto;
}

.vbx-ring {
position: absolute;

z-index: 3;

left:
var(--vbx-x);

top:
var(--vbx-y);

width:
calc(var(--vbx-r) * 2);

height:
calc(var(--vbx-r) * 2);

border:
1px solid
rgba(255,255,255,0.72);

border-radius:
50%;

pointer-events: none;

transform-origin:
center center;

transform:
translate(-50%,-50%)
rotate(var(--vbx-angle))
scaleX(var(--vbx-rx))
scaleY(var(--vbx-ry));

box-shadow:
0 0 0 1px
rgba(0,0,0,0.10);

will-change:
transform;
}

.vbx-root.vbx-editor
.vbx-frame {
cursor:
default;
}

.vbx-root.vbx-editor
.vbx-photo {
pointer-events: auto;
cursor: pointer;
}

@media
(max-width: 1199px) {

.vbx-root {
padding:
56px 16px;
}

.vbx-frame {
aspect-ratio:
3 / 2;
}
}

@media
(max-width: 767px) {

.vbx-root {
padding:
32px 14px;
}

.vbx-frame {
width:
calc(100vw - 28px);

aspect-ratio:
4 / 5;

cursor:
default;
}
}

@media
(prefers-reduced-motion: reduce) {

.vbx-ring {
display:
none !important;
}

.vbx-top-wrap {
-webkit-mask-image:
none !important;

mask-image:
none !important;
}

.vbx-frame {
cursor:
default !important;
}
}
</style>

<script>
(function () {
'use strict';

var R_IDLE = 0;
var R_OPEN = 150;
var R_LERP = 0.13;
var POS_LERP = 0.20;
var STRETCH = 0.004;
var STRETCH_MAX = 0.42;
var SPEED_THRESHOLD = 0.35;

function clamp(
value,
min,
max
) {
return value < min
? min
: value > max
? max
: value;
}

function lerp(
current,
target,
alpha,
delta
) {
var amount =
1 -
Math.pow(
1 - alpha,
delta / 16.67
);

return (
current +
(
target -
current
) *
amount
);
}

function isTouchPrimary() {
return window.matchMedia(
'(hover: none) and (pointer: coarse)'
).matches;
}

function XrayReveal(root) {
this.root =
root;

this.frame =
root.querySelector(
'.vbx-frame'
);

this.editorMode =
false;

try {
this.editorMode =
window.self !==
window.top;
}
catch (error) {
this.editorMode =
true;
}

if (
this.editorMode
) {
this.root.classList.add(
'vbx-editor'
);
}

this.fw = 0;
this.fh = 0;

this.rx = 0;
this.ry = 0;
this.rr = R_IDLE;

this.tx = 0;
this.ty = 0;
this.targetR = R_IDLE;

this.rStretch = 0;
this.rAngle = 0;

this.touchMode =
isTouchPrimary();

this.touchActive =
false;

this.running =
false;

this.rafHandle =
null;

this.lastTime =
null;

this.inViewport =
true;

this.tabVisible =
!document.hidden;

this._onPointerEnter =
this._onPointerEnter.bind(
this
);

this._onPointerLeave =
this._onPointerLeave.bind(
this
);

this._onPointerMove =
this._onPointerMove.bind(
this
);

this._onTouchStart =
this._onTouchStart.bind(
this
);

this._onTouchMove =
this._onTouchMove.bind(
this
);

this._onTouchEnd =
this._onTouchEnd.bind(
this
);

this._onVisibility =
this._onVisibility.bind(
this
);

this._loop =
this._loop.bind(
this
);

this._measure();

this._attach();

this._observe();

this._start();
}

XrayReveal.prototype._attach =
function () {

this.frame.addEventListener(
'pointerenter',
this._onPointerEnter,
{
passive: true
}
);

this.frame.addEventListener(
'pointerleave',
this._onPointerLeave,
{
passive: true
}
);

this.frame.addEventListener(
'pointermove',
this._onPointerMove,
{
passive: true
}
);

this.frame.addEventListener(
'touchstart',
this._onTouchStart,
{
passive: true
}
);

this.frame.addEventListener(
'touchmove',
this._onTouchMove,
{
passive: true
}
);

this.frame.addEventListener(
'touchend',
this._onTouchEnd,
{
passive: true
}
);

this.frame.addEventListener(
'touchcancel',
this._onTouchEnd,
{
passive: true
}
);

document.addEventListener(
'visibilitychange',
this._onVisibility
);
};

XrayReveal.prototype._observe =
function () {

var self =
this;

if (
'ResizeObserver' in window
) {
this._ro =
new ResizeObserver(
function () {
self._measure();
}
);

this._ro.observe(
this.frame
);
}

if (
'IntersectionObserver' in window
) {
this._io =
new IntersectionObserver(
function (entries) {

self.inViewport =
entries[0]
.isIntersecting;

if (
self.inViewport &&
self.tabVisible
) {
self._start();
}
else {
self._stop();
}
},
{
threshold: 0.05
}
);

this._io.observe(
this.root
);
}
};

XrayReveal.prototype._measure =
function () {

var rect =
this.frame
.getBoundingClientRect();

this.fw =
rect.width;

this.fh =
rect.height;

if (
this.rx === 0 &&
this.ry === 0
) {
this.rx =
this.fw *
0.5;

this.ry =
this.fh *
0.5;

this.tx =
this.rx;

this.ty =
this.ry;
}
};

XrayReveal.prototype._onPointerEnter =
function (event) {

if (
this.editorMode ||
this.touchMode
) {
return;
}

this._readPointer(
event
);

this.rx =
this.tx;

this.ry =
this.ty;

this.targetR =
R_OPEN;
};

XrayReveal.prototype._onPointerLeave =
function () {

if (
this.editorMode ||
this.touchMode
) {
return;
}

this.targetR =
R_IDLE;
};

XrayReveal.prototype._onPointerMove =
function (event) {

if (
this.editorMode ||
this.touchMode
) {
return;
}

this._readPointer(
event
);
};

XrayReveal.prototype._onTouchStart =
function (event) {

if (
this.editorMode ||
!this.touchMode ||
!event.touches ||
!event.touches.length
) {
return;
}

this.touchActive =
true;

this._readTouch(
event.touches[0]
);

this.rx =
this.tx;

this.ry =
this.ty;

this.targetR =
R_OPEN *
0.88;
};

XrayReveal.prototype._onTouchMove =
function (event) {

if (
!this.touchMode ||
!this.touchActive ||
!event.touches ||
!event.touches.length
) {
return;
}

this._readTouch(
event.touches[0]
);

this.targetR =
R_OPEN *
0.88;
};

XrayReveal.prototype._onTouchEnd =
function () {

if (
!this.touchMode
) {
return;
}

this.touchActive =
false;

this.targetR =
R_IDLE;
};

XrayReveal.prototype._readPointer =
function (event) {

var rect =
this.frame
.getBoundingClientRect();

this.tx =
event.clientX -
rect.left;

this.ty =
event.clientY -
rect.top;
};

XrayReveal.prototype._readTouch =
function (touch) {

var rect =
this.frame
.getBoundingClientRect();

this.tx =
clamp(
touch.clientX -
rect.left,
0,
rect.width
);

this.ty =
clamp(
touch.clientY -
rect.top,
0,
rect.height
);
};

XrayReveal.prototype._onVisibility =
function () {

this.tabVisible =
!document.hidden;

if (
this.inViewport &&
this.tabVisible
) {
this._start();
}
else {
this._stop();
}
};

XrayReveal.prototype._start =
function () {

if (
this.running
) {
return;
}

this.running =
true;

this.lastTime =
null;

this.rafHandle =
requestAnimationFrame(
this._loop
);
};

XrayReveal.prototype._stop =
function () {

this.running =
false;

if (
this.rafHandle !==
null
) {
cancelAnimationFrame(
this.rafHandle
);

this.rafHandle =
null;
}
};

XrayReveal.prototype._loop =
function (time) {

if (
!this.running
) {
return;
}

var delta =
this.lastTime ===
null
? 16.67
: Math.min(
time -
this.lastTime,
100
);

this.lastTime =
time;

var previousX =
this.rx;

var previousY =
this.ry;

this.rx =
lerp(
this.rx,
this.tx,
POS_LERP,
delta
);

this.ry =
lerp(
this.ry,
this.ty,
POS_LERP,
delta
);

this.rr =
lerp(
this.rr,
this.targetR,
R_LERP,
delta
);

var deltaX =
this.rx -
previousX;

var deltaY =
this.ry -
previousY;

var speed =
Math.hypot(
deltaX,
deltaY
) /
delta;

var rawStretch =
clamp(
speed *
STRETCH,
0,
STRETCH_MAX
);

this.rStretch =
lerp(
this.rStretch,
rawStretch,
0.20,
delta
);

if (
speed >
SPEED_THRESHOLD
) {
this.rAngle =
Math.atan2(
deltaY,
deltaX
) *
(
180 /
Math.PI
);
}

var scaleX =
1 +
this.rStretch;

var scaleY =
1 -
this.rStretch *
0.42;

var style =
this.root.style;

style.setProperty(
'--vbx-x',
this.rx.toFixed(2) +
'px'
);

style.setProperty(
'--vbx-y',
this.ry.toFixed(2) +
'px'
);

style.setProperty(
'--vbx-r',
this.rr.toFixed(2) +
'px'
);

style.setProperty(
'--vbx-rx',
scaleX.toFixed(4)
);

style.setProperty(
'--vbx-ry',
scaleY.toFixed(4)
);

style.setProperty(
'--vbx-angle',
this.rAngle.toFixed(2) +
'deg'
);

this.rafHandle =
requestAnimationFrame(
this._loop
);
};

var blocks =
document.querySelectorAll(
'[data-vbx-reveal]'
);

for (
var i = 0;
i < blocks.length;
i++
) {
var block =
blocks[i];

if (
block.hasAttribute(
'data-vbx-ready'
)
) {
continue;
}

block.setAttribute(
'data-vbx-ready',
'1'
);

new XrayReveal(
block
);
}

}());
</script>

</section><style data-vb-overrides="true">
.vbx-root {
background-color: #ffffff;
}
</style>
Скопировать код
Что выбрать, промпт или код?
Копируйте промпт, если хотите получить похожий результат, но ввести информацию о своём бизнесе и задать свои требования. Например, изменить кнопки, тексты, цвета, фотографии или другие элементы.
Копируйте код, если хотите получить точно такой же блок. Чтобы его изменить, придётся отредактировать html-код.
Начните создание сайта на Тильде с простой регистрации на платформе
Made on
Tilda