JustPaste
HomeCategoriesAboutDonateContactTerms of UsePrivacy Policy
JustPaste

Free online notepad — write and share instantly

Navigate

  • Home
  • Timeline
  • Categories

Info

  • About
  • Donate
  • Contact

Legal

  • Terms of Use
  • Privacy Policy

© 2026 JustPaste.app. All rights reserved.

Made with ♥ by JustPaste

Code | JustPaste.app
13 days ago12 views
👨‍💻Programming

Code

<!DOCTYPE html>
<html lang="fa">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

<title>Mini 3D Animator</title>

<style>
*{
    box-sizing:border-box;
    -webkit-tap-highlight-color:transparent;
}

html,body{
    margin:0;
    width:100%;
    height:100%;
    overflow:hidden;
    background:#101114;
    color:white;
    font-family:Arial,sans-serif;
}

#app{
    width:100vw;
    height:100vh;
    display:grid;
    grid-template-columns:135px 1fr 155px;
    grid-template-rows:45px 1fr 125px;
}

#top{
    grid-column:1/4;
    background:#181a1f;
    border-bottom:1px solid #333;
    display:flex;
    align-items:center;
    gap:6px;
    padding:5px;
    overflow-x:auto;
}

button{
    border:0;
    border-radius:6px;
    background:#292c33;
    color:white;
    padding:8px 10px;
    font-size:13px;
    white-space:nowrap;
}

button:active{
    background:#555;
}

.tool.active{
    background:#5965ff;
}

#left{
    background:#181a1f;
    border-right:1px solid #333;
    overflow:auto;
    padding:7px;
}

#right{
    background:#181a1f;
    border-left:1px solid #333;
    overflow:auto;
    padding:7px;
}

#viewport{
    position:relative;
    min-width:0;
    min-height:0;
    overflow:hidden;
}

#canvas{
    width:100%;
    height:100%;
    display:block;
}

#timeline{
    grid-column:1/4;
    background:#181a1f;
    border-top:1px solid #333;
    overflow:hidden;
    position:relative;
}

#timelineHeader{
    height:35px;
    display:flex;
    align-items:center;
    gap:8px;
    padding:4px 8px;
    background:#202229;
}

#track{
    position:absolute;
    left:0;
    right:0;
    bottom:0;
    top:35px;
    overflow-x:auto;
    overflow-y:hidden;
}

#frames{
    position:relative;
    height:90px;
    min-width:1000px;
}

.frame{
    position:absolute;
    top:0;
    height:90px;
    border-left:1px solid #30333b;
    color:#777;
    font-size:10px;
    padding-left:3px;
}

.key{
    position:absolute;
    width:12px;
    height:12px;
    background:#ffd84d;
    transform:rotate(45deg);
    border-radius:2px;
    top:42px;
}

#playhead{
    position:absolute;
    top:0;
    bottom:0;
    width:2px;
    background:#ff5555;
    z-index:10;
    pointer-events:none;
}

h3{
    margin:4px 0 8px;
    font-size:14px;
}

.item{
    background:#25282f;
    padding:8px;
    border-radius:6px;
    margin-bottom:5px;
    cursor:pointer;
    font-size:13px;
}

.item.selected{
    background:#505cff;
}

.row{
    display:flex;
    gap:4px;
    margin-bottom:5px;
}

input{
    width:100%;
    background:#101216;
    color:white;
    border:1px solid #393c45;
    border-radius:5px;
    padding:6px;
    font-size:12px;
}

label{
    display:block;
    font-size:11px;
    color:#aaa;
    margin:6px 0 3px;
}

.xyz{
    display:grid;
    grid-template-columns:repeat(3,1fr);
    gap:3px;
}

#info{
    position:absolute;
    left:8px;
    top:8px;
    background:rgba(0,0,0,.55);
    padding:6px 8px;
    border-radius:6px;
    font-size:11px;
    pointer-events:none;
}

#portrait{
    display:none;
}

@media(max-width:700px){
    #app{
        grid-template-columns:100px 1fr 115px;
        grid-template-rows:38px 1fr 100px;
    }

    button{
        padding:6px 7px;
        font-size:11px;
    }

    #left,#right{
        padding:5px;
    }

    .item{
        padding:6px;
        font-size:11px;
    }

    #timeline{
        font-size:11px;
    }
}

@media(orientation:portrait){
    #app{
        display:none;
    }

    #portrait{
        display:flex;
        position:fixed;
        inset:0;
        background:#101114;
        align-items:center;
        justify-content:center;
        flex-direction:column;
        gap:15px;
        font-size:20px;
        z-index:9999;
        text-align:center;
    }

    #portrait span{
        font-size:14px;
        color:#aaa;
    }
}
</style>
</head>

<body>

<div id="portrait">
    📱↔️
    <b>گوشی را افقی کن</b>
    <span>این موتور برای حالت Landscape ساخته شده</span>
</div>

<div id="app">

    <div id="top">

        <button onclick="addCube()">🧊 مکعب</button>
        <button onclick="addSphere()">⚪ کره</button>
        <button onclick="addCylinder()">🥫 استوانه</button>
        <button onclick="addCharacter()">🤖 کاراکتر</button>

        <button class="tool active" id="moveBtn" onclick="setTool('translate')">↔ حرکت</button>
        <button class="tool" id="rotateBtn" onclick="setTool('rotate')">🔄 چرخش</button>
        <button class="tool" id="scaleBtn" onclick="setTool('scale')">↗ اندازه</button>

        <button onclick="addKeyframe()">🔑 Keyframe</button>
        <button onclick="togglePlay()" id="play">▶ پخش</button>
        <button onclick="resetCamera()">🎥 دوربین</button>
        <button onclick="saveProject()">💾 ذخیره</button>
        <button onclick="loadProject()">📂 بارگذاری</button>

    </div>

    <aside id="left">
        <h3>🎬 صحنه</h3>
        <div id="objects"></div>

        <hr>

        <button style="width:100%" onclick="duplicateObject()">📑 کپی</button>
        <button style="width:100%;margin-top:5px" onclick="deleteObject()">🗑 حذف</button>
    </aside>

    <main id="viewport">
        <canvas id="canvas"></canvas>
        <div id="info">انتخاب یک جسم برای نمایش گیزمو</div>
    </main>

    <aside id="right">

        <h3>⚙ ویژگی‌ها</h3>

        <div id="properties">
            <div style="color:#777;font-size:12px">
                جسمی انتخاب نشده
            </div>
        </div>

    </aside>

    <div id="timeline">

        <div id="timelineHeader">
            <button onclick="setFrame(0)">⏮</button>
            <button onclick="previousFrame()">◀</button>
            <button onclick="nextFrame()">▶</button>

            <span>Frame:</span>

            <input id="frameInput"
                   value="0"
                   style="width:55px"
                   onchange="setFrame(Number(this.value))">

            <span>FPS:</span>

            <input id="fps"
                   value="30"
                   style="width:45px">

            <span id="status">آماده</span>
        </div>

        <div id="track">
            <div id="frames"></div>
        </div>

        <div id="playhead"></div>

    </div>

</div>


<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/TransformControls.js"></script>

<script>

let scene;
let camera;
let renderer;
let orbit;
let transform;

let selected = null;

let objects = [];
let frame = 0;
let playing = false;

let clock = new THREE.Clock();

const canvas = document.getElementById("canvas");


/* =========================
   شروع موتور
========================= */

scene = new THREE.Scene();

scene.background = new THREE.Color(0x101216);

scene.fog = new THREE.Fog(
    0x101216,
    15,
    80
);


camera = new THREE.PerspectiveCamera(
    60,
    1,
    0.1,
    1000
);

camera.position.set(
    7,
    5,
    9
);


renderer = new THREE.WebGLRenderer({
    canvas:canvas,
    antialias:true
});

renderer.setPixelRatio(
    Math.min(window.devicePixelRatio,2)
);

renderer.shadowMap.enabled = true;

renderer.shadowMap.type =
    THREE.PCFSoftShadowMap;


/* =========================
   نور
========================= */

const ambient =
    new THREE.HemisphereLight(
        0xffffff,
        0x222233,
        1.2
    );

scene.add(ambient);


const sun =
    new THREE.DirectionalLight(
        0xffffff,
        2
    );

sun.position.set(
    6,
    10,
    5
);

sun.castShadow = true;

sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;

scene.add(sun);


/* =========================
   زمین
========================= */

const floor =
    new THREE.Mesh(
        new THREE.PlaneGeometry(60,60),
        new THREE.MeshStandardMaterial({
            color:0x24262b,
            roughness:.8
        })
    );

floor.rotation.x =
    -Math.PI / 2;

floor.receiveShadow = true;

scene.add(floor);


/* =========================
   گرید سه بعدی
========================= */

const grid =
    new THREE.GridHelper(
        60,
        60,
        0x666666,
        0x333333
    );

grid.position.y = .01;

scene.add(grid);


/* =========================
   محورهای X Y Z
========================= */

const axes =
    new THREE.AxesHelper(4);

scene.add(axes);


/* =========================
   کنترل دوربین
========================= */

orbit =
    new THREE.OrbitControls(
        camera,
        renderer.domElement
    );

orbit.enableDamping = true;

orbit.dampingFactor = .08;

orbit.enableZoom = true;

orbit.zoomSpeed = 1.2;

orbit.minDistance = 2;

orbit.maxDistance = 80;


/* =========================
   Transform Gizmo
========================= */

transform =
    new THREE.TransformControls(
        camera,
        renderer.domElement
    );

scene.add(transform);

transform.setMode("translate");

transform.addEventListener(
    "dragging-changed",
    function(e){

        orbit.enabled = !e.value;

    }
);


/* =========================
   انتخاب
========================= */

const raycaster =
    new THREE.Raycaster();

const mouse =
    new THREE.Vector2();


canvas.addEventListener(
    "pointerdown",
    function(e){

        if(transform.dragging)
            return;

        const rect =
            canvas.getBoundingClientRect();

        mouse.x =
            ((e.clientX - rect.left) /
            rect.width) * 2 - 1;

        mouse.y =
            -((e.clientY - rect.top) /
            rect.height) * 2 + 1;

        raycaster.setFromCamera(
            mouse,
            camera
        );

        const hits =
            raycaster.intersectObjects(
                objects,
                true
            );

        if(hits.length){

            let obj = hits[0].object;

            while(
                obj.parent &&
                !objects.includes(obj)
            ){
                obj = obj.parent;
            }

            selectObject(obj);

        }

    }
);


/* =========================
   ساخت متریال
========================= */

function material(color){

    return new THREE.MeshStandardMaterial({

        color:color,

        roughness:.45,

        metalness:.15

    });

}


/* =========================
   اضافه کردن جسم
========================= */

function addObject(obj,name){

    obj.name = name;

    obj.position.set(
        0,
        1,
        0
    );

    obj.castShadow = true;

    obj.receiveShadow = true;

    scene.add(obj);

    objects.push(obj);

    selectObject(obj);

    updateObjectList();

}


/* =========================
   مکعب
========================= */

function addCube(){

    const geo =
        new THREE.BoxGeometry(
            2,
            2,
            2
        );

    const mesh =
        new THREE.Mesh(
            geo,
            material(0x4f7cff)
        );

    addObject(
        mesh,
        "Cube_" + objects.length
    );

}


/* =========================
   کره
========================= */

function addSphere(){

    const geo =
        new THREE.SphereGeometry(
            1.3,
            32,
            20
        );

    const mesh =
        new THREE.Mesh(
            geo,
            material(0xff6666)
        );

    addObject(
        mesh,
        "Sphere_" + objects.length
    );

}


/* =========================
   استوانه
========================= */

function addCylinder(){

    const geo =
        new THREE.CylinderGeometry(
            1,
            1,
            2.5,
            32
        );

    const mesh =
        new THREE.Mesh(
            geo,
            material(0x63d78b)
        );

    addObject(
        mesh,
        "Cylinder_" + objects.length
    );

}


/* =========================
   کاراکتر سه بعدی
========================= */

function addCharacter(){

    const group =
        new THREE.Group();

    group.name =
        "Character_" + objects.length;


    const body =
        new THREE.Mesh(
            new THREE.BoxGeometry(
                1.6,
                2.2,
                1
            ),
            material(0x5967ff)
        );

    body.position.y = 1.5;

    group.add(body);


    const head =
        new THREE.Mesh(
            new THREE.SphereGeometry(
                .85,
                24,
                16
            ),
            material(0xffc48b)
        );

    head.position.y = 3.2;

    group.add(head);


    const eye1 =
        new THREE.Mesh(
            new THREE.SphereGeometry(
                .12,
                12,
                8
            ),
            material(0x111111)
        );

    eye1.position.set(
        -.3,
        3.35,
        .75
    );

    group.add(eye1);


    const eye2 =
        eye1.clone();

    eye2.position.x = .3;

    group.add(eye2);


    const armGeo =
        new THREE.BoxGeometry(
            .45,
            1.8,
            .45
        );


    const arm1 =
        new THREE.Mesh(
            armGeo,
            material(0x5967ff)
        );

    arm1.position.set(
        -1.1,
        1.6,
        0
    );

    group.add(arm1);


    const arm2 =
        arm1.clone();

    arm2.position.x = 1.1;

    group.add(arm2);


    const legGeo =
        new THREE.BoxGeometry(
            .55,
            1.5,
            .55
        );


    const leg1 =
        new THREE.Mesh(
            legGeo,
            material(0x333b99)
        );

    leg1.position.set(
        -.45,
        -.25,
        0
    );

    group.add(leg1);


    const leg2 =
        leg1.clone();

    leg2.position.x = .45;

    group.add(leg2);


    group.position.set(
        0,
        0,
        0
    );

    group.traverse(
        function(o){

            if(o.isMesh){

                o.castShadow = true;

                o.receiveShadow = true;

            }

        }
    );

    scene.add(group);

    objects.push(group);

    selectObject(group);

    updateObjectList();

}


/* =========================
   انتخاب جسم
========================= */

function selectObject(obj){

    selected = obj;

    transform.attach(obj);

    updateObjectList();

    updateProperties();

}


/* =========================
   لیست اشیا
========================= */

function updateObjectList(){

    const box =
        document.getElementById(
            "objects"
        );

    box.innerHTML = "";

    objects.forEach(
        function(obj,i){

            const div =
                document.createElement(
                    "div"
                );

            div.className =
                "item" +
                (obj === selected ?
                " selected" : "");

            div.textContent =
                "◈ " + obj.name;

            div.onclick =
                function(){

                    selectObject(obj);

                };

            box.appendChild(div);

        }
    );

}


/* =========================
   ابزارها
========================= */

function setTool(mode){

    transform.setMode(mode);

    document
        .querySelectorAll(".tool")
        .forEach(
            b => b.classList.remove(
                "active"
            )
        );

    if(mode === "translate")
        document
            .getElementById("moveBtn")
            .classList.add("active");

    if(mode === "rotate")
        document
            .getElementById("rotateBtn")
            .classList.add("active");

    if(mode === "scale")
        document
            .getElementById("scaleBtn")
            .classList.add("active");

}


/* =========================
   ویژگی ها
========================= */

function updateProperties(){

    const box =
        document.getElementById(
            "properties"
        );

    if(!selected){

        box.innerHTML =
            "جسمی انتخاب نشده";

        return;

    }


    box.innerHTML = `

        <label>نام</label>

        <input
            value="${selected.name}"
            onchange="
                selected.name=this.value;
                updateObjectList();
            "
        >

        <label>موقعیت X Y Z</label>

        <div class="xyz">

            <input id="px">
            <input id="py">
            <input id="pz">

        </div>


        <label>چرخش X Y Z</label>

        <div class="xyz">

            <input id="rx">
            <input id="ry">
            <input id="rz">

        </div>


        <label>اندازه X Y Z</label>

        <div class="xyz">

            <input id="sx">
            <input id="sy">
            <input id="sz">

        </div>

    `;


    document.getElementById("px").value =
        selected.position.x.toFixed(2);

    document.getElementById("py").value =
        selected.position.y.toFixed(2);

    document.getElementById("pz").value =
        selected.position.z.toFixed(2);


    document.getElementById("rx").value =
        THREE.MathUtils.radToDeg(
            selected.rotation.x
        ).toFixed(1);

    document.getElementById("ry").value =
        THREE.MathUtils.radToDeg(
            selected.rotation.y
        ).toFixed(1);

    document.getElementById("rz").value =
        THREE.MathUtils.radToDeg(
            selected.rotation.z
        ).toFixed(1);


    document.getElementById("sx").value =
        selected.scale.x.toFixed(2);

    document.getElementById("sy").value =
        selected.scale.y.toFixed(2);

    document.getElementById("sz").value =
        selected.scale.z.toFixed(2);


    [
        "px","py","pz",
        "rx","ry","rz",
        "sx","sy","sz"
    ].forEach(
        function(id){

            document
                .getElementById(id)
                .onchange =
                applyProperties;

        }
    );

}


function applyProperties(){

    if(!selected)
        return;


    selected.position.set(

        Number(px.value),

        Number(py.value),

        Number(pz.value)

    );


    selected.rotation.set(

        THREE.MathUtils.degToRad(
            Number(rx.value)
        ),

        THREE.MathUtils.degToRad(
            Number(ry.value)
        ),

        THREE.MathUtils.degToRad(
            Number(rz.value)
        )

    );


    selected.scale.set(

        Number(sx.value),

        Number(sy.value),

        Number(sz.value)

    );

}


/* =========================
   کپی
========================= */

function duplicateObject(){

    if(!selected)
        return;

    const copy =
        selected.clone(true);

    copy.name =
        selected.name + "_Copy";

    copy.position.x += 2;

    scene.add(copy);

    objects.push(copy);

    selectObject(copy);

    updateObjectList();

}


/* =========================
   حذف
========================= */

function deleteObject(){

    if(!selected)
        return;

    scene.remove(selected);

    objects =
        objects.filter(
            o => o !== selected
        );

    transform.detach();

    selected = null;

    updateObjectList();

    updateProperties();

}


/* =========================
   Keyframe
========================= */

function addKeyframe(){

    if(!selected)
        return;

    if(!selected.userData.keys)
        selected.userData.keys = {};

    selected.userData.keys[frame] = {

        position:[
            selected.position.x,
            selected.position.y,
            selected.position.z
        ],

        rotation:[
            selected.rotation.x,
            selected.rotation.y,
            selected.rotation.z
        ],

        scale:[
            selected.scale.x,
            selected.scale.y,
            selected.scale.z
        ]

    };

    drawTimeline();

    document.getElementById(
        "status"
    ).textContent =
        "Keyframe ساخته شد";

}


/* =========================
   Timeline
========================= */

function drawTimeline(){

    const box =

⚠️Content was pasted as plain text and auto-formatted as a code block. Use the Code Block button in the editor for proper formatting.

← Back to timeline