إضافة مستكشف الأقسام التفاعلي ثلاثي الأبعاد (3D)

كود جيفري

عضو جديد
عضو انكور
إذا كنت تبحث عن طريقة مبتكرة لعرض أقسام منتداك بعيدًا عن القوائم التقليدية، فهذا الكود يقدم تجربة مختلفة تمامًا. فهو يعرض الأقسام الرئيسية على شكل كواكب صغيرة تفاعلية تدور في فضاء ثلاثي الأبعاد، يستطيع الزائر التنقل بينها والضغط على أي منها للانتقال إلى القسم مباشرة. ويمكنك التحكم الكامل في الأقسام المعروضة وأسمائها وروابطها وألوانها من خلال جزء بسيط في بداية الكود، مما يجعله مناسبًا لأي منتدى مهما كان عدد أقسامه.

مثال على الكود​

مستكشف أقسام ثلاثي الأبعاد يعرض أقسام المنتدى ككواكب تفاعلية تدور في الفضاء


شرح تركيب الكود​

تتكون عملية التركيب من خطوتين: إضافة كود HTML لتحديد مكان ظهور المستكشف، ثم إضافة كود JavaScript لتشغيله.

1- كود HTML
يمكنك وضعه في أي مكان يدعم أكواد HTML:

HTML:
<div id="forum-navigator-wrapper">
        
    <div id="forum-navigator-container">
                
        <div class="navigator-loader">
                        ...جاري بناء المستكشف...         
        </div>
            
    </div>
</div>

2- كود JavaScript
لوحة الإدارة ← عناصر إضافية ← إدارة أكواد Javascript، وأنشئ كودًا جديدًا مع اختيار إضافته في الصفحة الرئيسية:

JavaScript:
$(function() {
    const container = $('#forum-navigator-container');
    if (container.length === 0) { return; }

    const forumSections = [
        { name: 'القسم العام', url: '/f1-montada', color: 0xffd866 },
        { name: 'قسم التصميم', url: '/f2-montada', color: 0x4EC9B0 },
        { name: 'قسم البرمجة', url: '/f3-montada', color: 0xFC9867 },
        { name: 'قسم الألعاب', url: '/f4-montada', color: 0x8437E1 },
        { name: 'قسم المساعدة', url: '/f8-montada', color: 0xf1fa8c }
    ];

    var sCSS = '<style>' +
        '@keyframes AwesomeBB-gradient-animation { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }' +
        '#forum-navigator-wrapper { padding: 2px; background: linear-gradient(60deg, #8e44ad, #3498db, #1abc9c, #8e44ad); background-size: 300% 300%; animation: AwesomeBB-gradient-animation 10s ease-in-out infinite; border-radius: 14px; max-width: 900px; margin: 20px auto; }' +
        '#forum-navigator-container { position: relative; width: 100%; height: 400px; background: #0c0f1a; border-radius: 12px; cursor: grab; overflow: hidden; }' +
        '#forum-navigator-container:active { cursor: grabbing; }' +
        '.navigator-loader { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #bdc3c7; font-size: 16px; font-weight: 600; z-index: 10; text-align: center; }' +
        '#forum-navigator-container canvas { display: block; }' +
        '</style>';
    $('head').append(sCSS);

    const THREE_JS_URL = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
    const ORBIT_CONTROLS_URL = 'https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js';

    $.getScript(THREE_JS_URL, function() {
        $.getScript(ORBIT_CONTROLS_URL, function() {
            try {
                let scene, camera, renderer, planet, controls, stars;
                let clickableObjects = [], originalScales = new Map();
                let hoveredObj = null;
                const raycaster = new THREE.Raycaster();
                const mouse = new THREE.Vector2(-10, -10);
                
                function createTextSprite(message, color) {
                    const canvas = document.createElement('canvas');
                    const context = canvas.getContext('2d');
                    context.font = "Bold 32px Arial";
                    const textWidth = context.measureText(message).width;
                    canvas.width = textWidth + 20;
                    canvas.height = 52;
                    context.fillStyle = "rgba(12, 16, 26, 0.7)";
                    context.fillRect(0, 0, canvas.width, canvas.height);
                    const r = (color >> 16) & 255;
                    const g = (color >> 8) & 255;
                    const b = color & 255;
                    context.strokeStyle = `rgba(${r},${g},${b},0.5)`;
                    context.lineWidth = 2;
                    context.strokeRect(0, 0, canvas.width, canvas.height);
                    context.font = "Bold 32px Arial";
                    context.fillStyle = "rgba(255, 255, 255, 0.95)";
                    context.textAlign = 'center';
                    context.textBaseline = 'middle';
                    context.fillText(message, canvas.width / 2, canvas.height / 2);
                    const texture = new THREE.CanvasTexture(canvas);
                    const spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent: true });
                    const sprite = new THREE.Sprite(spriteMaterial);
                    sprite.scale.set(canvas.width / 18, canvas.height / 18, 1.0);
                    return sprite;
                }

                function onPointerMove(event) {
                    const rect = renderer.domElement.getBoundingClientRect();
                    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
                    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
                }

                function animate() {
                    requestAnimationFrame(animate);
                    controls.update();
                    planet.rotation.y += 0.0003;
                    stars.rotation.y += 0.0001;
                    raycaster.setFromCamera(mouse, camera);
                    const intersects = raycaster.intersectObjects(clickableObjects);

                    if (intersects.length > 0) {
                        let targetObject = intersects[0].object;
                        if (targetObject.isSprite) {
                            targetObject = targetObject.parent;
                        }

                        if (hoveredObj !== targetObject) {
                            if (hoveredObj) {
                                hoveredObj.material.emissive.setHex(hoveredObj.userData.originalEmissive);
                                hoveredObj.scale.copy(originalScales.get(hoveredObj));
                            }
                            hoveredObj = targetObject;
                            hoveredObj.material.emissive.setHex(0xffffff);
                            hoveredObj.scale.set(1.2, 1.2, 1.2);
                            container.css('cursor', 'pointer');
                        }
                    } else {
                        if (hoveredObj) {
                            hoveredObj.material.emissive.setHex(hoveredObj.userData.originalEmissive);
                            hoveredObj.scale.copy(originalScales.get(hoveredObj));
                            hoveredObj = null;
                            container.css('cursor', 'grab');
                        }
                    }
                    renderer.render(scene, camera);
                }

                function onClick() {
                    if (hoveredObj) { window.location.href = hoveredObj.userData.URL; }
                }

                function onWindowResize() {
                    if (camera && renderer) {
                        camera.aspect = container.width() / container.height();
                        camera.updateProjectionMatrix();
                        renderer.setSize(container.width(), container.height());
                    }
                }

                function init() {
                    scene = new THREE.Scene();
                    camera = new THREE.PerspectiveCamera(75, container.width() / container.height(), 0.1, 1000);
                    camera.position.set(0, 0, 35);
                    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
                    renderer.setSize(container.width(), container.height());
                    renderer.setPixelRatio(window.devicePixelRatio);
                    container.append(renderer.domElement);
                    controls = new THREE.OrbitControls(camera, renderer.domElement);
                    controls.enableDamping = true;
                    controls.dampingFactor = 0.05;
                    controls.minDistance = 25;
                    controls.maxDistance = 70;
                    controls.enablePan = false;
                    controls.autoRotate = true;
                    controls.autoRotateSpeed = 0.4;
                    const planetGeometry = new THREE.IcosahedronGeometry(10, 5);
                    const planetMaterial = new THREE.MeshStandardMaterial({ color: 0x95a5a6, metalness: 0.8, roughness: 0.4, wireframe: true, transparent: true, opacity: 0.2 });
                    planet = new THREE.Mesh(planetGeometry, planetMaterial);
                    scene.add(planet);
                    let starVertices = [];
                    for (let i = 0; i < 8000; i++) { starVertices.push((Math.random() - 0.5) * 1000, (Math.random() - 0.5) * 1000, (Math.random() - 0.5) * 1000); }
                    const starGeometry = new THREE.BufferGeometry();
                    starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
                    const starMaterial = new THREE.PointsMaterial({ color: 0xecf0f1, size: 0.15 });
                    stars = new THREE.Points(starGeometry, starMaterial);
                    scene.add(stars);
                    const samples = forumSections.length;
                    const radius = 15;
                    const offset = 2 / samples;
                    const increment = Math.PI * (3 - Math.sqrt(5));
                    for (let i = 0; i < samples; i++) {
                        const y = ((i * offset) - 1) + (offset / 2);
                        const r = Math.sqrt(1 - Math.pow(y, 2));
                        const phi = ((i + 1) % samples) * increment;
                        const x = Math.cos(phi) * r;
                        const z = Math.sin(phi) * r;
                        const moon = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2, 2), new THREE.MeshStandardMaterial({ color: forumSections[i].color, emissive: forumSections[i].color, emissiveIntensity: 1, metalness: 0.6, roughness: 0.5 }));
                        moon.position.set(x * radius, y * radius, z * radius);
                        moon.userData = { URL: forumSections[i].url, originalEmissive: moon.material.emissive.getHex() };
                        
                        const textSprite = createTextSprite(forumSections[i].name, forumSections[i].color);
                        textSprite.position.set(0, 2.2, 0);

                        moon.add(textSprite);
                        planet.add(moon);
                        clickableObjects.push(moon);
                        clickableObjects.push(textSprite);
                        originalScales.set(moon, moon.scale.clone());
                    }
                    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
                    scene.add(ambientLight);
                    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
                    directionalLight.position.set(5, 10, 7.5);
                    scene.add(directionalLight);
                    
                    renderer.domElement.addEventListener('pointermove', onPointerMove);
                    renderer.domElement.addEventListener('click', onClick);
                    
                    $(window).on('resize', onWindowResize);
                    container.children('.navigator-loader').fadeOut(500, function() { $(this).remove(); });
                }

                init();
                animate();

            } catch (e) {
                container.text('حدث خطأ غير متوقع أثناء تشغيل المستكشف: ' + e.message);
            }
        }).fail(function() {
            container.text("فشل تحميل مكتبة التحكم (OrbitControls.js).");
        });
    }).fail(function() {
        container.text("فشل تحميل المكتبة الأساسية (Three.js).");
    });
});

كيفية تخصيص الأقسام
لتعديل الأقسام التي تظهر في المستكشف، عدّل الجزء المخصص لذلك في بداية كود الجافا سكريبت، وهو يشبه المثال التالي:

JavaScript:
const forumSections = [
    { name: 'القسم العام', url: '/f1-montada', color: 0xffd866 },
    { name: 'قسم المساعدة', url: '/f8-montada', color: 0xf1fa8c }
];

في كل سطر عدّل قيمة name إلى اسم القسم، وقيمة url إلى رابط القسم الصحيح، وقيمة color إلى كود اللون الذي تفضله. ولإضافة قسم جديد انسخ السطر التالي والصقه داخل القائمة مع تعديل بياناته، ولحذف قسم احذف السطر الخاص به:

JavaScript:
{ name: 'القسم العام', url: '/f1-montada', color: 0xffd866 },

إضافة تمنح الصفحة الرئيسية لمنتداك هوية بصرية مميزة وتجربة تصفح لا تُنسى. كل التقدير لصاحب الكود على هذا العمل الإبداعي.

صاحب الكود: Lufy
 

رأيك جزء من المعرفة.

أضف تجربتك أو سؤالك، فالنقاش يكمل الفكرة.

في هذا الموضوع

صاحب النقاش

كود جيفري
كود جيفريعضو جديد

معرفة تكبر بالنقاش

وجدت معلومة مفيدة؟ أضف تجربتك أو سؤالك ليساعد النقاش أعضاء آخرين.

أكمل رحلتك

كود يخفي أي جزء من المساهمة عن الزواركود الروابط أو الزيارات الأخيرة في المنتدىتمييز المساهمات حسب الجهاز (الهواتف والكمبيوترات)
عودة
أعلى