jQuery仿新浪微博@功能特效

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jQuery仿新浪微博@功能特效</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
        }
        
        body {
            background-color: #f6f6f6;
            padding: 20px;
            color: #333;
        }
        
        .container {
            max-width: 600px;
            margin: 0 auto;
            background-color: #fff;
            border-radius: 8px;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
            padding: 20px;
        }
        
        h1 {
            font-size: 22px;
            margin-bottom: 20px;
            color: #333;
            text-align: center;
        }
        
        .note {
            background-color: #f8f4e5;
            padding: 12px 15px;
            border-left: 4px solid #e67e22;
            margin-bottom: 20px;
            border-radius: 0 4px 4px 0;
            font-size: 14px;
            line-height: 1.5;
        }
        
        .note a {
            color: #e67e22;
            text-decoration: none;
        }
        
        .note a:hover {
            text-decoration: underline;
        }
        
        .weibo-editor {
            position: relative;
            margin-bottom: 20px;
        }
        
        .editor-box {
            width: 100%;
            min-height: 120px;
            padding: 12px;
            border: 1px solid #e6e6e6;
            border-radius: 4px;
            font-size: 14px;
            line-height: 1.5;
            outline: none;
            resize: none;
            transition: border-color 0.3s;
        }
        
        .editor-box:focus {
            border-color: #3498db;
        }
        
        .mention-tag {
            color: #3498db;
            background-color: #e8f4fd;
            padding: 0 2px;
            border-radius: 2px;
            cursor: pointer;
        }
        
        .mention-tag:hover {
            text-decoration: underline;
        }
        
        .suggest-list {
            position: absolute;
            left: 0;
            top: 100%;
            width: 100%;
            max-height: 200px;
            overflow-y: auto;
            background-color: #fff;
            border: 1px solid #e6e6e6;
            border-radius: 0 0 4px 4px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            z-index: 100;
            display: none;
        }
        
        .suggest-item {
            padding: 8px 12px;
            cursor: pointer;
            display: flex;
            align-items: center;
            transition: background-color 0.2s;
        }
        
        .suggest-item:hover {
            background-color: #f5f5f5;
        }
        
        .suggest-item.active {
            background-color: #e8f4fd;
        }
        
        .user-avatar {
            width: 32px;
            height: 32px;
            border-radius: 50%;
            margin-right: 10px;
            object-fit: cover;
        }
        
        .user-info {
            flex: 1;
        }
        
        .user-name {
            font-size: 14px;
            font-weight: bold;
            margin-bottom: 2px;
        }
        
        .user-desc {
            font-size: 12px;
            color: #999;
        }
        
        .footer {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding-top: 15px;
            border-top: 1px solid #f0f0f0;
        }
        
        .btn {
            padding: 6px 16px;
            border-radius: 4px;
            font-size: 14px;
            cursor: pointer;
            border: none;
            outline: none;
            transition: all 0.3s;
        }
        
        .btn-publish {
            background-color: #ff8200;
            color: #fff;
        }
        
        .btn-publish:hover {
            background-color: #f77000;
        }
        
        .btn-cancel {
            background-color: transparent;
            color: #666;
            border: 1px solid #d9d9d9;
        }
        
        .btn-cancel:hover {
            border-color: #999;
        }
        
        .counter {
            font-size: 12px;
            color: #999;
        }
        
        .counter.warning {
            color: #ff8200;
        }
        
        .counter.error {
            color: #f44336;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>jQuery仿新浪微博@功能特效</h1>
        
        <div class="weibo-editor">
            <div class="editor-box" id="weiboEditor" contenteditable="true" placeholder="分享你的新鲜事..."></div>
            <div class="suggest-list" id="suggestList">
                <!-- 建议列表将通过JS动态生成 -->
            </div>
        </div>
        
        <div class="footer">
            <div class="counter" id="wordCounter">0/140</div>
            <div>
                <button class="btn btn-cancel">取消</button>
                <button class="btn btn-publish">发布</button>
            </div>
        </div>
    </div>

    <!-- 引入jQuery -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    
    <script>
        $(document).ready(function() {
            const $editor = $('#weiboEditor');
            const $suggestList = $('#suggestList');
            const $wordCounter = $('#wordCounter');
            const MAX_LENGTH = 140;
            
            // 模拟用户数据
            const users = [
                { id: 1, name: '张三', avatar: 'https://randomuser.me/api/portraits/men/1.jpg', desc: '前端开发工程师' },
                { id: 2, name: '李四', avatar: 'https://randomuser.me/api/portraits/women/2.jpg', desc: 'UI设计师' },
                { id: 3, name: '王五', avatar: 'https://randomuser.me/api/portraits/men/3.jpg', desc: '产品经理' },
                { id: 4, name: '赵六', avatar: 'https://randomuser.me/api/portraits/women/4.jpg', desc: '后端开发工程师' },
                { id: 5, name: '钱七', avatar: 'https://randomuser.me/api/portraits/men/5.jpg', desc: '测试工程师' },
                { id: 6, name: '孙八', avatar: 'https://randomuser.me/api/portraits/women/6.jpg', desc: '运维工程师' },
                { id: 7, name: '周九', avatar: 'https://randomuser.me/api/portraits/men/7.jpg', desc: '全栈工程师' },
                { id: 8, name: '吴十', avatar: 'https://randomuser.me/api/portraits/women/8.jpg', desc: '数据分析师' }
            ];
            
            // 当前是否在输入@
            let isMentioning = false;
            // 当前输入的@关键字
            let mentionKeyword = '';
            // 当前选中的建议项索引
            let selectedIndex = -1;
            
            // 初始化编辑器
            function initEditor() {
                updateWordCounter();
                
                // 监听输入事件
                $editor.on('input', function() {
                    updateWordCounter();
                    checkMention();
                });
                
                // 监听键盘事件
                $editor.on('keydown', function(e) {
                    // 如果建议列表显示,处理上下键和回车键
                    if ($suggestList.is(':visible')) {
                        switch(e.keyCode) {
                            case 38: // 上箭头
                                e.preventDefault();
                                moveSelection(-1);
                                break;
                            case 40: // 下箭头
                                e.preventDefault();
                                moveSelection(1);
                                break;
                            case 13: // 回车
                                e.preventDefault();
                                selectUser();
                                break;
                            case 27: // ESC
                                hideSuggestList();
                                break;
                        }
                    }
                    
                    // 输入@时开始提及
                    if (e.keyCode === 50 && e.shiftKey) { // @符号
                        setTimeout(() => {
                            startMention();
                        }, 10);
                    }
                });
                
                // 点击编辑器外部时隐藏建议列表
                $(document).on('click', function(e) {
                    if (!$(e.target).closest('.weibo-editor').length) {
                        hideSuggestList();
                    }
                });
            }
            
            // 更新字数统计
            function updateWordCounter() {
                const text = $editor.text();
                const length = text.length;
                
                $wordCounter.text(`${length}/${MAX_LENGTH}`);
                
                if (length > MAX_LENGTH) {
                    $wordCounter.addClass('error');
                } else if (length > MAX_LENGTH - 20) {
                    $wordCounter.addClass('warning');
                } else {
                    $wordCounter.removeClass('warning error');
                }
            }
            
            // 检查是否正在输入@
            function checkMention() {
                if (!isMentioning) return;
                
                const text = $editor.text();
                const cursorPos = getCaretPosition($editor[0]);
                const textBeforeCursor = text.substring(0, cursorPos);
                
                // 获取@后面的内容
                const atIndex = textBeforeCursor.lastIndexOf('@');
                if (atIndex === -1) {
                    isMentioning = false;
                    hideSuggestList();
                    return;
                }
                
                mentionKeyword = textBeforeCursor.substring(atIndex + 1).trim();
                
                if (mentionKeyword.length > 0) {
                    showSuggestList(mentionKeyword);
                } else {
                    hideSuggestList();
                }
            }
            
            // 开始@提及
            function startMention() {
                isMentioning = true;
                mentionKeyword = '';
                selectedIndex = -1;
            }
            
            // 显示建议列表
            function showSuggestList(keyword) {
                const filteredUsers = users.filter(user => 
                    user.name.includes(keyword) || 
                    (user.desc && user.desc.includes(keyword))
                    .slice(0, 8);
                
                if (filteredUsers.length === 0) {
                    hideSuggestList();
                    return;
                }
                
                // 生成建议列表HTML
                let html = '';
                filteredUsers.forEach((user, index) => {
                    html += `
                        <div class="suggest-item ${index === selectedIndex ? 'active' : ''}" data-id="${user.id}">
                            <img src="${user.avatar}" class="user-avatar" alt="${user.name}">
                            <div class="user-info">
                                <div class="user-name">${user.name}</div>
                                <div class="user-desc">${user.desc}</div>
                            </div>
                        </div>
                    `;
                });
                
                $suggestList.html(html).show();
                
                // 绑定点击事件
                $suggestList.find('.suggest-item').on('click', function() {
                    const $item = $(this);
                    selectUser($item.index());
                });
            }
            
            // 隐藏建议列表
            function hideSuggestList() {
                $suggestList.hide();
                isMentioning = false;
                selectedIndex = -1;
            }
            
            // 移动选择项
            function moveSelection(step) {
                const $items = $suggestList.find('.suggest-item');
                if ($items.length === 0) return;
                
                selectedIndex += step;
                
                if (selectedIndex < 0) {
                    selectedIndex = $items.length - 1;
                } else if (selectedIndex >= $items.length) {
                    selectedIndex = 0;
                }
                
                $items.removeClass('active');
                $items.eq(selectedIndex).addClass('active');
                
                // 滚动到可见区域
                const $selectedItem = $items.eq(selectedIndex);
                const containerHeight = $suggestList.height();
                const itemHeight = $selectedItem.outerHeight();
                const itemTop = $selectedItem.position().top;
                
                if (itemTop < 0) {
                    $suggestList.scrollTop($suggestList.scrollTop() + itemTop);
                } else if (itemTop + itemHeight > containerHeight) {
                    $suggestList.scrollTop($suggestList.scrollTop() + (itemTop + itemHeight - containerHeight));
                }
            }
            
            // 选择用户
            function selectUser(index) {
                if (index !== undefined) {
                    selectedIndex = index;
                }
                
                if (selectedIndex === -1) return;
                
                const $items = $suggestList.find('.suggest-item');
                if (selectedIndex >= $items.length) return;
                
                const $selectedItem = $items.eq(selectedIndex);
                const userId = $selectedItem.data('id');
                const userName = $selectedItem.find('.user-name').text();
                
                // 获取当前文本和光标位置
                const text = $editor.text();
                const cursorPos = getCaretPosition($editor[0]);
                const textBeforeCursor = text.substring(0, cursorPos);
                
                // 找到@位置
                const atIndex = textBeforeCursor.lastIndexOf('@');
                if (atIndex === -1) return;
                
                // 替换@后面的内容为用户名
                const textBeforeAt = text.substring(0, atIndex);
                const textAfterAt = text.substring(cursorPos);
                
                // 创建mention标签
                const mentionTag = document.createElement('span');
                mentionTag.className = 'mention-tag';
                mentionTag.textContent = `@${userName}`;
                mentionTag.dataset.userId = userId;
                
                // 保存当前选区
                const selection = window.getSelection();
                const range = selection.getRangeAt(0);
                
                // 替换文本
                $editor.empty();
                $editor.append(document.createTextNode(textBeforeAt));
                $editor.append(mentionTag);
                $editor.append(document.createTextNode(textAfterAt));
                
                // 恢复光标位置
                const newRange = document.createRange();
                newRange.setStartAfter(mentionTag);
                newRange.collapse(true);
                selection.removeAllRanges();
                selection.addRange(newRange);
                
                hideSuggestList();
                updateWordCounter();
            }
            
            // 获取光标位置
            function getCaretPosition(editableDiv) {
                let caretPos = 0;
                const selection = window.getSelection();
                
                if (selection.rangeCount > 0) {
                    const range = selection.getRangeAt(0);
                    const preCaretRange = range.cloneRange();
                    preCaretRange.selectNodeContents(editableDiv);
                    preCaretRange.setEnd(range.endContainer, range.endOffset);
                    caretPos = preCaretRange.toString().length;
                }
                
                return caretPos;
            }
            
            // 初始化
            initEditor();
        });
    </script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值