Codrops 원본
Interactive Video Projection Mapping with Three.js
섹션 · MIT
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
SOURCE FILES
원본 코드 읽기
수집한 원본 소스와 실행 안내를 함께 제공합니다.
css/reset.css
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}css/style.css
html {
font-size: calc(100vw / 1920 * 10);
--gap: 4rem;
}
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
}
* {
line-height: 100%;
box-sizing: border-box;
}
button {
border: none;
background: transparent;
}
canvas#sketch {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 1;
transition: background-color 1s ease-in-out .5s;
/* background: #000; */
}
ul.btns {
position: fixed;
z-index: 6;
top: 50svh;
right: 3svw;
transform: translate(0, -50%);
display: flex;
flex-direction: column;
gap: 0.8svh;
--offset: -1vw;
}
.btns button {
width: 6svh;
height: 6svh;
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
background-color: rgba(0, 0, 0, 0.2);
color: white;
font-family: var(--serif);
transition: background-color 0.2s ease, color 0.2s ease,
border-color 0.2s ease;
cursor: pointer;
}
.btns button:hover {
background-color: black;
border-color: black;
}
li.btns__item:nth-child(odd) {
transform: translateX(calc(var(--offset) * 2));
}
.btns button.active {
background-color: white;
color: black;
pointer-events: none;
}
ul.btns {
top: auto;
bottom: 5svh;
right: auto;
left: 50vw;
flex-direction: row;
gap: 13rem;
transform: translate(-50%, 0%);
}
.btns button {
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
width: 9svh;
height: 9svh;
background-color: rgba(0, 0, 0, 0.05);
}
.btns button svg {
height: 2.7rem;
}
.btns button svg path {
fill: white;
}
.btns button.active {
background: rgba(0, 0, 0, 0.2);
border: 2px solid rgba(255, 255, 255, 0.4);
}
li.btns__item:nth-child(odd) {
transform: translateY(calc(var(--offset) * 3));
}
[data-current="heart"] {
background-color: #e19800 !important;
}
[data-current="codrops"] {
background-color: #00a00b !important;
}
[data-current="smile"] {
background-color: #b90000 !important;
}
.partners {
position: fixed;
z-index: 2;
cursor: pointer;
top: var(--gap);
left: var(--gap);
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4rem;
}
.partners__each svg {
max-height: 2rem;
width: 11.1rem;
}
.partners__dot {
width: 0.7rem;
height: 0.7rem;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
}함께 쓰는 파일 7개 보기
index.html
<html>
<head>
<meta charset="utf-8">
<title>Video Projection - Codrops | VWLAB </title>
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="css/reset.css" rel="stylesheet" type="text/css">
<link href="css/style.css" rel="stylesheet" type="text/css">
</head>
<body class="body">
<div class="partners">
<div class="partners__each">
<a href="https://tympanus.net/codrops/" target="_blank">
<svg class="ct-logo-img" width="100%" viewBox="0 0 500 56" xmlns="http://www.w3.org/2000/svg"><title>Codrops</title><path d="M35.278 55.47c-5.79 0-10.896-.712-15.315-2.134-4.42-1.422-8.102-3.378-11.048-5.867-2.946-2.489-5.169-5.422-6.667-8.8C.749 35.29 0 31.646 0 27.735c0-3.912.75-7.556 2.248-10.934 1.498-3.378 3.72-6.312 6.667-8.8 2.946-2.49 6.629-4.445 11.048-5.868C24.383.711 29.487 0 35.278 0c5.486 0 10.312.584 14.477 1.752 4.165 1.169 7.721 2.782 10.667 4.839 2.946 2.057 5.258 4.457 6.934 7.2a24.18 24.18 0 0 1 3.276 8.839H54.174c-.304-1.118-.876-2.223-1.714-3.315s-2.02-2.082-3.543-2.971c-1.524-.89-3.403-1.613-5.639-2.172-2.235-.559-4.901-.838-8-.838s-5.829.368-8.19 1.105c-2.363.736-4.331 1.752-5.906 3.048-1.575 1.295-2.756 2.819-3.543 4.571-.787 1.753-1.181 3.645-1.181 5.677 0 2.032.394 3.924 1.181 5.676.787 1.753 1.968 3.277 3.543 4.572 1.575 1.295 3.543 2.311 5.905 3.048 2.362.736 5.092 1.105 8.191 1.105 3.099 0 5.765-.28 8-.839 2.236-.558 4.115-1.282 5.639-2.171 1.524-.889 2.705-1.88 3.543-2.972.838-1.092 1.41-2.197 1.714-3.314h16.458a24.533 24.533 0 0 1-3.276 8.877c-1.676 2.768-3.975 5.168-6.896 7.2-2.92 2.032-6.476 3.632-10.667 4.8-4.19 1.168-9.029 1.753-14.515 1.753Zm76.958 0c-5.943 0-11.15-.712-15.62-2.134-4.47-1.422-8.203-3.39-11.2-5.905-2.997-2.514-5.245-5.46-6.743-8.838-1.499-3.378-2.248-6.998-2.248-10.858 0-3.912.75-7.543 2.248-10.896 1.498-3.353 3.746-6.286 6.743-8.8 2.997-2.515 6.73-4.483 11.2-5.906C101.087.711 106.294 0 112.237 0c5.944 0 11.15.711 15.62 2.133 4.47 1.423 8.204 3.391 11.2 5.906 2.998 2.514 5.246 5.447 6.744 8.8 1.499 3.353 2.248 6.984 2.248 10.896 0 3.86-.75 7.48-2.248 10.858-1.498 3.378-3.746 6.324-6.743 8.838-2.997 2.515-6.73 4.483-11.2 5.905-4.47 1.422-9.677 2.134-15.62 2.134Zm0-13.334c3.251 0 6.096-.369 8.534-1.105 2.438-.737 4.458-1.753 6.058-3.048 1.6-1.295 2.794-2.82 3.58-4.572.788-1.752 1.182-3.644 1.182-5.676s-.394-3.924-1.181-5.677c-.787-1.752-1.981-3.276-3.581-4.571-1.6-1.296-3.62-2.312-6.058-3.048-2.438-.737-5.283-1.105-8.534-1.105-3.25 0-6.095.368-8.533 1.105-2.439.736-4.458 1.752-6.058 3.048-1.6 1.295-2.794 2.819-3.58 4.571-.788 1.753-1.182 3.645-1.182 5.677 0 2.032.394 3.924 1.181 5.676.787 1.753 1.981 3.277 3.581 4.572 1.6 1.295 3.62 2.311 6.058 3.048 2.438.736 5.282 1.105 8.533 1.105Zm44.5 12.267V1.067h27.582c5.436 0 10.223.673 14.363 2.019 4.14 1.346 7.594 3.213 10.363 5.6 2.768 2.388 4.863 5.207 6.286 8.458 1.422 3.25 2.133 6.781 2.133 10.59 0 3.81-.711 7.34-2.133 10.592-1.423 3.25-3.518 6.07-6.286 8.457-2.769 2.388-6.223 4.255-10.363 5.6-4.14 1.347-8.927 2.02-14.363 2.02h-27.582Zm16.458-13.258h11.124c2.743 0 5.156-.343 7.239-1.029 2.083-.685 3.822-1.625 5.22-2.819 1.396-1.193 2.45-2.603 3.161-4.229.711-1.625 1.067-3.403 1.067-5.333 0-1.93-.356-3.708-1.067-5.334-.71-1.625-1.765-3.035-3.162-4.229-1.397-1.193-3.136-2.133-5.22-2.819-2.082-.686-4.495-1.028-7.238-1.028h-11.124v26.82Zm113.76 13.258h-19.658l-5.714-13.715c-.66-1.575-1.6-2.68-2.82-3.315-1.219-.635-2.946-.952-5.18-.952h-10.973v17.982h-16.458V1.067h35.812c3.81 0 7.111.419 9.905 1.257 2.794.838 5.105 2.02 6.934 3.543 1.828 1.524 3.187 3.327 4.076 5.41.889 2.082 1.333 4.368 1.333 6.857 0 2.185-.368 4.23-1.104 6.134-.737 1.905-1.74 3.556-3.01 4.953a15.205 15.205 0 0 1-4.496 3.39 14.75 14.75 0 0 1-5.486 1.524v1.372c1.423.05 2.705.216 3.848.495 1.143.28 2.21.75 3.2 1.41.99.66 1.905 1.549 2.743 2.666.839 1.118 1.639 2.515 2.4 4.191l4.648 10.134Zm-27.049-27.659c2.642 0 4.61-.597 5.905-1.79 1.296-1.194 1.943-2.756 1.943-4.686 0-1.93-.647-3.505-1.943-4.724-1.295-1.22-3.263-1.829-5.905-1.829h-18.21v13.03h18.21Zm67.358 28.726c-5.943 0-11.15-.712-15.62-2.134-4.47-1.422-8.203-3.39-11.2-5.905-2.997-2.514-5.245-5.46-6.744-8.838-1.498-3.378-2.247-6.998-2.247-10.858 0-3.912.749-7.543 2.247-10.896 1.499-3.353 3.747-6.286 6.744-8.8 2.997-2.515 6.73-4.483 11.2-5.906C316.113.711 321.32 0 327.263 0s11.15.711 15.62 2.133c4.47 1.423 8.204 3.391 11.2 5.906 2.998 2.514 5.245 5.447 6.744 8.8 1.498 3.353 2.248 6.984 2.248 10.896 0 3.86-.75 7.48-2.248 10.858-1.499 3.378-3.746 6.324-6.743 8.838-2.997 2.515-6.73 4.483-11.201 5.905-4.47 1.422-9.677 2.134-15.62 2.134Zm0-13.334c3.251 0 6.096-.369 8.534-1.105 2.438-.737 4.457-1.753 6.057-3.048 1.6-1.295 2.794-2.82 3.582-4.572.787-1.752 1.18-3.644 1.18-5.676s-.393-3.924-1.18-5.677c-.788-1.752-1.981-3.276-3.582-4.571-1.6-1.296-3.619-2.312-6.057-3.048-2.438-.737-5.283-1.105-8.534-1.105-3.25 0-6.095.368-8.534 1.105-2.438.736-4.457 1.752-6.057 3.048-1.6 1.295-2.794 2.819-3.581 4.571-.788 1.753-1.181 3.645-1.181 5.677 0 2.032.393 3.924 1.18 5.676.788 1.753 1.982 3.277 3.582 4.572 1.6 1.295 3.62 2.311 6.057 3.048 2.439.736 5.283 1.105 8.534 1.105Zm79.244-41.07c7.772 0 13.538 1.715 17.297 5.144 3.759 3.429 5.638 8.038 5.638 13.83 0 5.79-1.88 10.4-5.638 13.828-3.76 3.43-9.525 5.144-17.297 5.144h-18.286v15.39h-16.458V1.068h34.744Zm-.762 24.993c2.439 0 4.255-.56 5.448-1.677 1.194-1.117 1.79-2.565 1.79-4.343s-.596-3.225-1.79-4.343c-1.193-1.117-3.01-1.676-5.448-1.676h-18.439v12.039h18.44Zm62.33 29.41c-5.842 0-10.807-.52-14.897-1.561-4.089-1.042-7.403-2.477-9.943-4.305-2.54-1.829-4.394-4-5.562-6.515-1.168-2.514-1.753-5.245-1.753-8.19v-.915h16.763v.533c0 1.22.254 2.362.762 3.429.508 1.067 1.359 1.98 2.553 2.743 1.193.762 2.768 1.359 4.724 1.79 1.955.432 4.406.648 7.352.648 3.15 0 5.715-.152 7.696-.457 1.981-.305 3.518-.699 4.61-1.181 1.092-.483 1.841-1.054 2.248-1.714.406-.66.61-1.321.61-1.982 0-1.473-.674-2.577-2.02-3.314-1.346-.737-3.124-1.257-5.334-1.562-2.21-.305-4.71-.508-7.505-.61a265.366 265.366 0 0 1-8.61-.457 84.867 84.867 0 0 1-8.61-1.028c-2.794-.483-5.295-1.283-7.505-2.4-2.21-1.118-3.987-2.616-5.333-4.496-1.347-1.88-2.02-4.318-2.02-7.315 0-2.285.521-4.432 1.562-6.438 1.042-2.006 2.781-3.759 5.22-5.257 2.438-1.499 5.676-2.693 9.714-3.582C456.836.444 461.877 0 467.922 0c5.69 0 10.54.52 14.553 1.562 4.013 1.041 7.29 2.45 9.83 4.229 2.54 1.778 4.393 3.86 5.562 6.248a17.109 17.109 0 0 1 1.752 7.62v.685h-16.763v-.686c0-.914-.254-1.816-.762-2.705s-1.346-1.676-2.514-2.362c-1.168-.685-2.705-1.232-4.61-1.638-1.905-.406-4.254-.61-7.048-.61-3.098 0-5.613.14-7.543.42-1.93.279-3.442.66-4.534 1.143-1.092.482-1.828 1.028-2.21 1.638-.38.61-.57 1.219-.57 1.828 0 1.27.672 2.197 2.018 2.781 1.346.585 3.124 1.004 5.334 1.258 2.21.254 4.711.406 7.505.457 2.794.05 5.664.165 8.61.343 2.946.177 5.816.52 8.61 1.028 2.794.508 5.296 1.334 7.505 2.477 2.21 1.143 3.988 2.68 5.334 4.61 1.346 1.93 2.019 4.419 2.019 7.466 0 2.388-.546 4.661-1.638 6.82-1.092 2.159-2.883 4.038-5.372 5.638-2.489 1.6-5.778 2.87-9.867 3.81-4.09.94-9.105 1.41-15.049 1.41Z" fill="white"></path></svg>
</a>
</div>
<div class="partners__dot"></div>
<div class="partners__each">
<a href="https://vwlab.io" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 5370 1254" fill="none">
<path d="M2427.28 939.963L2442.07 926.35V880.183L2474.63 847.63H2519.61L2533.82 832.833V234.443L2566.96 201.298H2613.13L2644.5 234.443V832.833L2613.13 865.978H2566.96L2552.16 880.183V926.35L2519.61 957.719H2474.63L2460.42 972.516V1018.68L2427.28 1050.05H2381.11L2349.74 1018.68V972.516L2335.54 957.719H2289.37L2258 926.35V880.183L2243.2 865.978H2197.04L2165.67 832.833V234.443L2197.04 201.298H2243.2L2275.76 234.443V832.833L2289.37 847.63H2335.54L2368.09 880.183V926.35L2381.11 939.963H2427.28Z" fill="#fff"/>
<path d="M3078.82 926.35V234.443L3111.96 201.298H3158.13L3189.5 234.443V926.35L3158.13 957.719H3111.96L3097.17 972.516V1018.68L3064.61 1050.05H3019.63L2987.08 1018.68V972.516L2972.28 957.719H2926.11L2913.09 972.516V1018.68L2880.54 1050.05H2834.37L2803 1018.68V972.516L2788.21 957.719H2742.04L2710.67 926.35V234.443L2742.04 201.298H2788.21L2820.76 234.443V926.35L2834.37 939.963H2880.54L2894.74 926.35V696.109L2926.11 663.555H2972.28L3005.43 696.109V926.35L3019.63 939.963H3064.61L3078.82 926.35Z" fill="#fff"/>
<path d="M3494.2 1026.97L3471.12 1050.05H3425.54L3393.58 1017.5V971.332L3416.66 948.249L3393.58 925.166V878.999L3425.54 846.446H3471.12L3494.2 869.529L3518.47 846.446H3563.45L3596 878.999V925.166L3572.92 948.249L3596 971.332V1017.5L3563.45 1050.05H3518.47L3494.2 1026.97ZM3494.2 934.636L3481.18 948.249L3494.2 961.27L3509 948.249L3494.2 934.636Z" fill="#fff"/>
<path d="M3800.67 1018.68V234.443L3833.23 201.298H3879.39L3911.95 234.443V926.35L3888.86 949.433L3902.48 963.046L3925.56 939.963H4248.13L4280.69 972.516V1018.68L4248.13 1050.05H3833.23L3800.67 1018.68Z" fill="#fff"/>
<path d="M4470.56 1050.05L4439.19 1017.5V971.332L4424.4 957.719H4378.23L4345.68 925.166V787.258L4378.23 754.113H4424.4L4439.19 741.091V694.925L4470.56 662.372H4700.8L4723.89 685.455L4738.09 671.842L4715.6 648.758V510.851L4700.8 497.237H4470.56L4456.95 510.851V556.425L4424.4 589.57H4378.23L4345.68 556.425V510.851L4378.23 477.705H4424.4L4439.19 464.684V418.517L4470.56 385.964H4700.8L4733.36 418.517V464.684L4748.15 477.705H4793.14L4825.69 510.851V1017.5L4793.14 1050.05H4470.56ZM4470.56 938.779H4700.8L4723.89 961.27L4738.09 948.249L4715.6 925.166V787.258L4738.09 764.175L4723.89 750.562L4700.8 773.645H4470.56L4456.95 787.258V925.166L4470.56 938.779Z" fill="#fff"/>
<path d="M5277.17 1017.5L5245.81 1050.05H4922.05L4890.68 1017.5V233.259L4922.05 201.298H4968.21L5000.77 233.259V371.167L4977.68 394.25L4991.3 409.047L5014.38 385.964H5245.81L5277.17 417.334V463.5L5291.97 477.705H5338.14L5369.51 510.851V925.166L5338.14 956.535H5291.97L5277.17 971.332V1017.5ZM4977.68 948.249L4991.3 961.27L5014.38 938.779H5245.81L5258.83 925.166V510.851L5245.81 496.054H5014.38L4991.3 472.97L4977.68 486.583L5000.77 510.851V925.166L4977.68 948.249Z" fill="#fff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M979.99 416.523C1095.01 416.523 1188.25 323.281 1188.25 208.261C1188.25 93.2418 1095.01 0 979.99 0C864.971 0 771.729 93.2418 771.729 208.261C771.729 223.888 773.45 239.112 776.713 253.756C783.007 282.003 781.022 312.71 763.417 335.68V335.68C736.515 370.78 685.392 374.261 643.43 360.299C622.749 353.418 600.626 349.692 577.634 349.692C462.614 349.692 369.372 442.934 369.372 557.953C369.372 577.31 372.013 596.05 376.954 613.833C392.556 669.982 400.192 734.993 364.742 781.246V781.246C329.703 826.962 265.861 836.944 208.261 836.944V836.944C93.2418 836.944 0 930.186 0 1045.21C0 1160.23 93.2418 1253.47 208.261 1253.47C323.281 1253.47 416.523 1160.23 416.523 1045.21C416.523 1040.37 416.358 1035.57 416.033 1030.81C411.575 965.51 407.568 893.317 447.385 841.367L472.793 808.217C496.795 776.9 538.177 766.215 577.634 766.215V766.215V766.215C617.087 766.215 658.466 776.9 682.466 808.214L711.12 845.6C751.782 898.652 748.632 972.234 746.692 1039.05C746.633 1041.09 746.603 1043.15 746.603 1045.21C746.603 1160.23 839.845 1253.47 954.864 1253.47C1069.88 1253.47 1163.13 1160.23 1163.13 1045.21C1163.13 930.186 1069.88 836.944 954.864 836.944C953.883 836.944 952.903 836.951 951.925 836.965C892.774 837.783 826.707 828.462 790.721 781.51V781.51C755.156 735.107 762.835 669.877 778.399 613.522C783.285 595.832 785.895 577.198 785.895 557.953C785.895 551.348 785.588 544.815 784.986 538.368C782.073 507.134 785.831 474.384 804.913 449.487V449.487C835.41 409.696 892.324 403.811 941.609 412.993C954.049 415.311 966.878 416.523 979.99 416.523ZM1550.01 690.595C1550.01 805.614 1456.77 898.856 1341.75 898.856C1226.73 898.856 1133.49 805.614 1133.49 690.595C1133.49 575.575 1226.73 482.333 1341.75 482.333C1456.77 482.333 1550.01 575.575 1550.01 690.595Z" fill="#fff"/>
</svg>
</a>
</div>
</div>
<ul class="btns">
<li class="btns__item">
<button class="active" data-id="heart">
<svg width="100%" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1079.4 875.4">
<path d="M.9,256.8c1.6-5.7,1.7-11.7,1.8-17.7.9-12.2,3.5-24.3,5.8-36.2,4.9-21.2,11.9-42.3,22.6-61.3,5.1-10.7,10.4-21,17.8-30.3,5.4-7.3,10.6-14.2,17.6-20.3,4.2-4.3,7-9.3,11.3-13.3,5.5-4.5,11.8-10.9,17.6-15.8,7.7-5.4,15.7-10.9,22.8-16.9C178.9,8.1,252.8-9.3,323.3,5c70.7,8.5,132.7,52.8,173.4,109.7,13.5,18.8,25.1,39.4,35.1,61.1,1.9,4.9,6.5,9.6,10.2,3,4-8.1,6.4-17,12.2-24,5.9-10.9,12-22.2,19.4-32.4,4-6.6,9.7-11.6,15-17.3,5.6-6.8,10.7-14.2,17-20.5,37-38.2,83.4-67.6,136-76.1,20.5-5,41.5-8.6,62.8-6.9,12.4.7,25.1-.8,37.5.8,11.3,2.4,22.5,3.6,33.9,5.4,44.8,13.8,92.1,35.7,125.3,69.4,7.2,7.9,14.1,17.1,21.9,24.4,7.6,8.9,13.2,18.9,19.5,28.7,8,12.2,12.5,26.4,18.2,39.7,2.4,7.3,5.1,14.3,7.3,21.8,1.7,6.5,1.9,14,3.9,20.2,2.5,12.5,4.9,25,4.8,37.9.6,5.7,2.7,10.4,2.8,16.2,0,8-1.6,15.9-2.7,23.8-2.9,14.6,0,31-4.5,45.4-4.1,8.3-5.6,18.3-8.8,26.8-3.5,7.5-5.7,15.7-9.5,23.1-5.5,12-8.8,25.2-16.2,36.2-10.2,19.2-21.4,37.8-35.1,54.7-8.6,12.2-17.6,24.2-28.3,34.5-3.3,4-4.5,7.7-8,11.6-4.2,4.5-10.3,9.1-15.1,13.7-5.2,4.3-9.1,10.1-14,14.1-3.6,3-7.9,5.1-11.3,8.4-3.4,3.2-6.4,8-9.7,11-3.3,3.1-6.8,4.7-10.3,7.5-4.6,3.9-8.6,8.6-13.6,11.9-11.5,8.8-22.3,19-34.4,26.9-7.9,7.2-16.1,13.2-24.7,19.2-19.9,15.5-39.7,31-60,46-38.2,29.7-76.9,58.7-114.4,89.2-4.4,4.1-9.4,6.7-13.7,11.1-2.9,3.1-5.7,6.7-8.7,9.7-12.6,12-28.3,22.2-41.5,34.6-14.6,13.3-26,30-40.9,42.9-8.8,8.1-16.2,10.5-23.8-.9-6.3-7.6-13.5-17-19.9-23.6-3.9-3.5-9.3-4.9-13.5-8.6-5.9-5.1-11.1-13.2-16.4-18.8-4.4-5.1-10.1-6.7-15-10.9-3.3-2.7-6.1-6.9-9.5-9.9-15.5-12.3-30.5-25.3-45.9-37.9-4.1-3.7-9.6-6.2-13.9-9.8-8.6-7.9-17.3-14.9-26.6-21.7-24.8-16.9-47.7-36.3-72.2-53.6-28.1-21.2-55.8-43.1-83.9-64.3-11.2-8.4-21.9-17.7-32.7-26.7-4.3-3.5-8.7-5.9-12.5-9.7-3.8-3.8-6.9-8.6-10.8-12.3-4.5-4.2-9.3-5.6-13.8-9.7-7.6-7.8-13.4-16.2-22-23.3-5.3-4.8-11.2-9.5-16.1-14.6-4-4.3-5.4-9.2-9.6-13.5-5.5-5.3-11.4-10.1-15.4-16.7-7-8.6-14.4-18.2-20.5-27.6-23.7-32.7-40.2-70.6-49.9-109.7-2.1-9-4.2-17.9-5.7-27-1.3-6.2-3.2-12.2-2.9-18.7,0-6,.2-12.6,0-18.5-.3-7.8-2-16-.1-23.7v-.2Z"/>
</svg>
</button>
</li>
<li class="btns__item">
<button data-id="codrops">
<svg width="100%" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 217.6 346">
<path d="M217.6,211.9v12.2c0,24.2-6.3,48.3-19.6,68.5-17.2,26.2-48,48-78.5,53.2-6.5.3-13.2,0-19.4-.8-26.4-3.9-59.5-23-75.4-44.8C-46.5,202.9,53.8,84.4,106.8,0c35,52,102.5,150.8,110.8,211.9Z"/>
</svg>
</button>
</li>
<li class="btns__item">
<button data-id="smile">
<svg width="100%" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1080 1080">
<path d="M176.2,141.2C495.5-153.5,1021.8,40.6,1075.6,469.4c55.6,443.1-421.8,762-808.2,536.1C-48.1,821-92,388.7,176.2,141.2ZM720.6,269.4c-77.2,3.9-82.7,198.4-19,224.2,66.7,27.1,87.8-72.8,85.4-119.9-1.8-34.5-21.9-106.5-66.4-104.3ZM326.3,486.8c45.6,39.8,84.3-21.5,92.5-62.1,8.4-41.4,1.2-124.7-39.8-148.1-49.9-28.5-82.7,46.1-87.6,85.5s7.4,100.7,34.9,124.7ZM797.9,771.4c55.6-63.9,91.9-151.5,87.6-237.1l-9.2-8.5c-27.4-12.4-22.6,55.6-25.7,71.5-65.2,337.2-549,339.1-619.7,12.9-3.6-16.5-4.6-69.5-11.8-78.9-12.6-16.3-29.2-1.9-28.3,19.3,12.3,308.5,403.3,454.6,607.1,220.7Z"/>
</svg>
</button>
</li>
</ul>
<canvas id="sketch" data-current="heart"></canvas>
<script type="module" src="js/app.js"></script>
</body>
</html>js/Models.js
import * as THREE from 'three'
import { gsap } from 'gsap'
export default class Models {
constructor(gl_app) {
this.scene = gl_app.scene
this.group = new THREE.Group()
this.scene.add(this.group)
// Grid parameters
this.gridSize = 24
this.spacing = 0.65
this.grids_config = [
{
id: 'heart',
mask: `heart.jpg`,
video: `fruits_trail_squared-transcode.mp4`
},
{
id: 'codrops',
mask: `codrops.jpg`,
video: `KinectCube_1350-transcode.mp4`
},
{
id: 'smile',
mask: `smile.jpg`,
video: `infinte-grid_squared-transcode.mp4`
},
]
this.grids_config.forEach((config, index) => this.createMask(config, index))
this.group.scale.setScalar(0.15)
this.is_ready = true
this.grids = []
// this.createMask()
}
createMask(config, index) {
// Create a canvas to read mask pixel data
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const maskImage = new Image()
maskImage.crossOrigin = 'anonymous'
maskImage.onload = () => {
// Get original image dimensions to preserve aspect ratio
const originalWidth = maskImage.width
const originalHeight = maskImage.height
const aspectRatio = originalWidth / originalHeight
// Calculate grid dimensions based on aspect ratio
this.gridWidth
this.gridHeight
if (aspectRatio > 1) {
// Image is wider than tall
this.gridWidth = this.gridSize
this.gridHeight = Math.round(this.gridSize / aspectRatio)
} else {
// Image is taller than wide or square
this.gridHeight = this.gridSize
this.gridWidth = Math.round(this.gridSize * aspectRatio)
}
canvas.width = this.gridWidth
canvas.height = this.gridHeight
ctx.drawImage(maskImage, 0, 0, this.gridWidth, this.gridHeight)
const imageData = ctx.getImageData(0, 0, this.gridWidth, this.gridHeight)
this.data = imageData.data
this.createGrid(config, index)
}
maskImage.src = `../images/${config.mask}`
}
createVideoTexture(config, index) {
this.video = document.createElement('video')
this.video.src = `../videos/${config.video}`
this.video.crossOrigin = 'anonymous'
this.video.loop = true
this.video.muted = true
this.video.play()
// Create video texture
this.videoTexture = new THREE.VideoTexture(this.video)
this.videoTexture.minFilter = THREE.LinearFilter
this.videoTexture.magFilter = THREE.LinearFilter
this.videoTexture.colorSpace = THREE.SRGBColorSpace
this.videoTexture.wrapS = THREE.ClampToEdgeWrap
this.videoTexture.wrapT = THREE.ClampToEdgeWrap
// Create material with video texture
this.material = new THREE.MeshBasicMaterial({
map: this.videoTexture,
side: THREE.FrontSide
})
}
createGrid(config, index) {
this.createVideoTexture(config, index)
const grid_group = new THREE.Group()
this.group.add(grid_group)
for (let x = 0; x < this.gridSize; x++) {
for (let y = 0; y < this.gridSize; y++) {
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
// Get pixel color from mask (sample at grid position)
// Flip Y coordinate to match image orientation
const flippedY = this.gridHeight - 1 - y
const pixelIndex = (flippedY * this.gridWidth + x) * 4
const r = this.data[pixelIndex]
const g = this.data[pixelIndex + 1]
const b = this.data[pixelIndex + 2]
// Calculate brightness (0 = black, 255 = white)
const brightness = (r + g + b) / 3
// Only create box if pixel is dark (black shows, white hides)
if (brightness < 128) { // Threshold for black vs white
// Create individual geometry for each box to have unique UV mapping
// Calculate UV coordinates for this specific box
const uvX = x / this.gridSize
const uvY = y / this.gridSize // Remove the flip to match correct orientation
const uvWidth = 1 / this.gridSize
const uvHeight = 1 / this.gridSize
// Get the UV attribute
const uvAttribute = geometry.attributes.uv
const uvArray = uvAttribute.array
// Map each face of the box to show the same portion of video
// We'll focus on the front face (face 4) for the main projection
for (let i = 0; i < uvArray.length; i += 2) {
// Map all faces to the same UV region for consistency
uvArray[i] = uvX + (uvArray[i] * uvWidth) // U coordinate
uvArray[i + 1] = uvY + (uvArray[i + 1] * uvHeight) // V coordinate
}
// Mark the attribute as needing update
uvAttribute.needsUpdate = true
/* ________ CONTINUING */
/* ________ CONTINUING */
/* ________ CONTINUING */
const mesh = new THREE.Mesh(geometry, this.material);
mesh.position.x = (x - (this.gridSize - 1) / 2) * this.spacing;
mesh.position.y = (y - (this.gridSize - 1) / 2) * this.spacing;
mesh.position.z = 0;
grid_group.add(mesh);
}
}
}
grid_group.name = config.id
this.grids.push(grid_group);
this.initInteractions()
}
initInteractions() {
this.current = 'heart'
this.old = null
this.is_animating = false
this.duration = 1
this.DOM = {
$btns: document.querySelectorAll('.btns__item button'),
$canvas: document.querySelector('canvas')
}
this.grids.forEach(grid => {
if(grid.name != this.current) {
grid.children.forEach(mesh => mesh.scale.setScalar(0))
}
})
this.bindEvents()
}
bindEvents() {
this.DOM.$btns.forEach(($btn, index) => {
$btn.addEventListener('click', () => {
if (this.is_animating) return
this.is_animating = true
this.DOM.$btns.forEach(($btn, btnIndex) => {
btnIndex === index ? $btn.classList.add('active') : $btn.classList.remove('active')
})
this.old = this.current
this.current = `${$btn.dataset.id}`
this.revealGrid()
this.hideGrid()
})
})
}
revealGrid() {
// Filter the current grid based on this.current value
const grid = this.grids.find(item => item.name === this.current);
this.DOM.$canvas.dataset.current = `${this.current}`
const tl = gsap.timeline({ delay: this.duration * 0.25, defaults: { ease: 'power1.out', duration: this.duration } })
grid.children.forEach((child, index) => {
tl
.to(child.scale, { x: 1, y: 1, z: 1, ease: 'power3.inOut' }, index * 0.001)
.to(child.position, { z: 0 }, '<')
})
}
hideGrid() {
// Filter the current grid based on this.old value
const grid = this.grids.find(item => item.name === this.old);
const tl = gsap.timeline({
defaults: { ease: 'power1.out', duration: this.duration },
onComplete: () => { this.is_animating = false }
})
grid.children.forEach((child, index) => {
tl
.to(child.scale, { x: 0, y: 0, z: 0, ease: 'power3.inOut' }, index * 0.001)
.to(child.position, {
z: 6, onComplete: () => {
gsap.set(child.scale, { x: 0, y: 0, z: 0 })
gsap.set(child.position, { z: - 6 })
}
}, '<')
})
}
}
js/app.js
import gsap from 'gsap'
import GLApp from './gl-app';
class App {
constructor() {
this.gl_app = new GLApp()
}
}
new App();js/gl-app.js
import * as THREE from 'three'
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import Models from './Models';
export default class GLApp {
constructor() {
window.app = this
/*----------------------------------------------*/
// Renderer */
/*----------------------------------------------*/
this.canvas = document.querySelector('canvas#sketch');
this.pixelRatio = Math.min(window.devicePixelRatio, 2)
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
alias: true,
alpha: true
});
this.renderer.setClearColor(0x000000, 0)
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(this.pixelRatio)
/*----------------------------------------------*/
// Scene & Camera */
/*----------------------------------------------*/
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 100);
this.camera.position.z = 6;
/*----------------------------------------------*/
// OrbitControl */
/*----------------------------------------------*/
this.controls = new OrbitControls(this.camera, this.canvas)
this.controls.enableDamping = true
/*----------------------------------------------*/
// Load HDR */
/*----------------------------------------------*/
this.ambient_light = new THREE.AmbientLight( 0xffffff ); // soft
this.scene.add( this.ambient_light );
this.dir_light = new THREE.DirectionalLight(0xffffff, 10);
this.dir_light.position.set(5, 5, 5);
this.scene.add( this.dir_light );
/*----------------------------------------------*/
// Resize */
/*----------------------------------------------*/
function onResize() {
that.camera.aspect = window.innerWidth / window.innerHeight;
that.camera.updateProjectionMatrix();
that.pixelRatio = Math.min(window.devicePixelRatio, 2)
that.renderer.setSize(window.innerWidth, window.innerHeight);
that.renderer.setPixelRatio(that.pixelRatio)
}
window.addEventListener('resize', onResize, false);
/*----------------------------------------------*/
// Models */
/*----------------------------------------------*/
this.models = new Models(this)
/*----------------------------------------------*/
// Loop */
/*----------------------------------------------*/
let that = this
function animate() {
requestAnimationFrame(animate)
that.renderer.render(that.scene, that.camera)
that.controls.update()
}
animate()
}
}
vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
assetsInclude: ['**/*.glb'],
});Original author attribution실행 안내·자료
Interactive Video Projection Mapping with Three.js
Original author: Victor Work
Published by Codrops. Copyright (c) 2026 Codrops.
License: MIT, pursuant to the publisher's downloadable-demo grant.
Keep CODROPS-MIT-2026.txt and the original project notices with copies.
Third-party media exceptions and credits are recorded separately in the source inventory.
Bundled dependency licenses실행 안내·자료
GSAP standard license snapshot
Source: https://gsap.com/community/standard-license/
Retrieved: 2026-09-22T04:22:49.089Z
Original distribution copyright and version headers remain in the runtime code.
Standard "No Charge" GSAP License
I. DEFINITIONS
“GSAP License” means the terms and conditions of this GSAP Software License Agreement.
"GSAP Products" means any Software made available at gsap.com (https://gsap.com) or any successor sites, including but not limited to the GSAP animation library and related plugins, tools, or extensions.
"Permitted Uses" means the implementation and/or use of GSAP Products on any website, web application, or digital interface by any person or entity (which may include, for clarity, those of companies that compete with Webflow in other areas of business).
"Prohibited Uses" means any implementation and/or use of GSAP Products in tools that allow users to build visual animations without code that encourages, induces, or materially assists in creating a solution that competes with Webflow’s visual animation building capabilities.
"Competitive Products" means any software, tool, or service that enables users to create, edit, or manage animations through a visual interface or builder similar to Webflow (https://webflow.com).
II. GRANT OF LICENSE
Subject to the terms and conditions of this GSAP License, Webflow grants you a non-exclusive, worldwide license to use, reproduce, display, and implement GSAP Products solely for Permitted Uses.
III. RESTRICTIONS
You may not:
Use any GSAP Products for any Prohibited Uses without prior written consent;
Reverse engineer any GSAP Products for the purpose of creating Competitive Products;
Remove or alter any proprietary notices or branding from GSAP Products.
IV. OWNERSHIP AND INTELLECTUAL PROPERTY
All intellectual property rights in GSAP Products, including but not limited to copyright, patents, trademarks, and trade secrets, remain the exclusive property of Webflow. This GSAP License does not transfer any ownership rights in GSAP Products to you.
V. TERMINATION
Webflow may terminate this GSAP License and revoke your access in its discretion if you fail to comply with any of these terms and conditions. Upon termination, you must cease all use of GSAP Products and destroy all copies in your possession.
VI. MISCELLANEOUS PROVISIONS
General: This GSAP License is incorporated into and subject to Webflow’s Terms of Service available here (https://webflow.com/legal/terms) ("Terms of Service"). In the event of any conflict or inconsistency between this GSAP License and the Terms of Service, the terms of this GSAP License shall govern in relation to your use of any GSAP Products.
Amendments: Webflow reserves the right to update or modify this GSAP License at any time by posting the revised terms on this website, provided that any such updates or modifications shall not result in any material degradation to the security, integrity, or functionality of any GSAP Products. You understand and agree that your continued use of any GSAP Products after such revisions to this GSAP License constitutes your acceptance of this GSAP License as revised. If you do not accept the revised GSAP License, you are prohibited from using versions of the GSAP Products released after the effective date of the revised GSAP License (as well as any updates made to previous versions). Notwithstanding, you may continue using previous versions of GSAP Products under the applicable terms licensed to you prior to the effective date of the revised GSAP License (for clarity, excluding any updates made thereto).
No Waiver: Failure of Webflow to enforce any provision of this GSAP License shall not constitute a waiver of future enforcement of that or any other provision.
FAQ
Is it acceptable for AI tools like ChatGPT, Cursor, Lovable, Webstudio, etc. to generate GSAP code?
Absolutely! AI-generated code is not a "Prohibited Use".
What if a WordPress plugin or theme or other niche tool allows users to create GSAP-driven effects through a visual interface? Is that prohibited?
We want to encourage developers to build on top of GSAP, including visual tools that don't directly compete with Webflow's rich animation-building capabilities. If you are not sure if your product might be considered a "Prohibited Use", feel free to contact us (https://gsap.com/contact) so we can talk through it!
Can I really use GSAP in commercial projects without paying anything?
Yes, really! Commercial usage is covered under the standard license. All of GSAP including the plugins that were formerly "members-only" like SplitText (https://gsap.com/docs/v3/Plugins/SplitText/) and MorphSVG (https://gsap.com/docs/v3/Plugins/MorphSVGPlugin) can be used in commercial projects at no charge. Enjoy! 💚
Effective date: April 30, 2025
Last modified date: May 30, 2025
Copyright (©) 2025, Webflow
three@0.172.0 — LICENSE
The MIT License
Copyright © 2010-2024 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
마스크 비율과 정사각 격자 반복의 치수 전제
입력·상태·출력·수명과 실제 결과를 명시합니다.
- 이 예제에서는
- createMask는 원본 비율에 따라 gridWidth·gridHeight를 정합니다. createGrid의 반복과 UV 분할은 둘 다 gridSize를 사용하면서 픽셀 읽기는 gridWidth·gridHeight를 사용합니다.
