js实现抖音话题发布

抖音话题发布实现

//jsonp

(function () {
  const isPlainObject = function isPlainObject(obj) {
    let proto, Ctor;
    if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false;
    proto = Object.getPrototypeOf(obj);
    if (!proto) return true;
    Ctor = proto.hasOwnProperty('constructor') && proto.constructor;
    return typeof Ctor === "function" && Ctor === Object;
  };

  const stringify = function stringify(obj) {
    let keys = Reflect.ownKeys(obj),
      str = ``;
    keys.forEach(key => {
      str += `&${key}=${obj[key]}`;
    });
    return str.substring(1);
  };

  const jsonp = function jsonp(url, options) {
    if (typeof url !== "string") throw new TypeError('url is not a string~');
    if (!isPlainObject(options)) options = {};
    let { params, jsonpName } = Object.assign(
      {
        params: null,
        jsonpName: 'callback'
      },
      options
    );
    return new Promise((resolve, reject) => {
      let name = `jsonp${+new Date()}`,
        script;
      window[name] = function (data) {
        resolve(data);
        if (script) document.body.removeChild(script);
        Reflect.deleteProperty(window, name);
      };
      if (params && isPlainObject(params)) {
        params = stringify(params);
        url += `${url.includes('?') ? '&' : '?'}${params}`;
      }
      url += `${url.includes('?') ? '&' : '?'}${jsonpName}=${name}`;
      script = document.createElement('script');
      script.src = url;
      script.async = true;
      script.onerror = () => reject();
      document.body.appendChild(script);
    });
  };

  /* 暴露API */
  if (typeof module === 'object' && typeof module.exports === 'object') module.exports = jsonp;
  if (typeof window !== 'undefined') window.jsonp = jsonp;
})();
//utils.js
(function () {
    const clearTimer = function clearTimer(timer) {
        if (timer) clearTimeout(timer);
        return null;
    };

    // 函数防抖
    const debounce = function debounce(func, wait, immediate) {
        if (typeof func !== 'function') throw new TypeError('func is not a function~');
        if (typeof wait === 'boolean') immediate = wait;
        if (typeof wait !== 'number') wait = 300;
        if (typeof immediate !== "boolean") immediate = false;
        let timer = null;
        return function operate(...params) {
            let now = !timer && immediate,
                result;
            timer = clearTimer(timer);
            timer = setTimeout(() => {
                if (!immediate) func.call(this, ...params);
                timer = clearTimer(timer);
            }, wait);
            if (now) result = func.call(this, ...params);
            return result;
        };
    };

    // 函数节流
    const throttle = function throttle(func, wait) {
        if (typeof func !== 'function') throw new TypeError('func is not a function~');
        if (typeof wait !== 'number') wait = 300;
        let timer = null,
            previous = 0;
        return function operate(...params) {
            let now = +new Date(),
                remaining = wait - (now - previous),
                result;
            if (remaining <= 0) {
                result = func.call(this, ...params);
                previous = +new Date();
                timer = clearTimer(timer);
            } else if (!timer) {
                timer = setTimeout(() => {
                    func.call(this, ...params);
                    previous = +new Date();
                    timer = clearTimer(timer);
                }, remaining);
            }
            return result;
        };
    };

    let utils = {
        debounce,
        throttle
    };

    /* 导出模块 */
    if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
    if (typeof window !== 'undefined') window.utils = utils;
})();

//index.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>面试</title>
    <!-- IMPORT CSS -->
    <link rel="stylesheet" href="reset.min.css">
    <style>
        html,
        body {
            height: 100%;
            font-size: 14px;
        }

        /* 文本框 */
        .box {
            position: relative;
            box-sizing: border-box;
            margin: 0 auto;
            width: 300px;
        }

        .inpText {
            display: block;
            box-sizing: border-box;
            padding: 5px;
            width: 100%;
            height: 60px;
        }

        /* 模糊匹配 */
        .search {
            display: none;
            position: absolute;
            top: 60px;
            box-sizing: border-box;
            width: 100%;
            background: rgb(252, 238, 240);
        }

        .search li {
            padding: 0 5px;
            line-height: 30px;
            cursor: pointer;
        }

        .search li:hover {
            background: rgb(221, 239, 250);
        }

        /* 按钮 */
        .handle {
            padding: 5px 0;
        }

        .handle button {
            padding: 5px 10px;
            border: none;
            background: lightskyblue;
            cursor: pointer;
        }

        .handle button.upload {
            background: lightpink;
        }
    </style>
</head>

<body>
    <div class="box">
        <textarea class="inpText"></textarea>
        <ul class="search"></ul>
        <div class="handle">
            <button class="talk">#话题</button>
            <button class="upload">上传</button>
        </div>
    </div>

    <!-- IMPORT JS -->
    <script src="utils.js"></script>
    <script src="jsonp.js"></script>
    <script>
        (function () {
            const inpText = document.querySelector('.inpText'),
                search = document.querySelector('.search'),
                talk = document.querySelector('.talk'),
                upload = document.querySelector('.upload');
            const { debounce, throttle } = utils,
                reg = /#([^#\s]*)/;
            let prev = '';

            /* 从百度服务器,根据话题关键词获取匹配结果 */
            const queryData = async function queryData(match) {
                // 从百度获取模糊匹配的数据
                try {
                    let { g: arr } = await jsonp('https://www.baidu.com/sugrec', {
                        params: {
                            prod: 'pc',
                            wd: match
                        },
                        jsonpName: 'cb'
                    });
                    if (arr && arr.length > 0) {
                        // 有匹配的结果
                        search.style.display = 'block';
                        let str = ``;
                        arr.forEach((item, index) => {
                            if (index < 5 && item.q) {
                                str += `<li>${item.q}</li>`;
                            }
                        });
                        search.innerHTML = str;
                        return;
                    }
                } catch (_) { }
                // 请求失败或者没有匹配结果
                search.innerHTML = '';
                search.style.display = 'none';
            };

            /* 基于事件委托实现匹配结果的输入 */
            document.addEventListener('click', function (ev) {
                let target = ev.target,
                    targetTag = target.tagName,
                    parent = target.parentNode;
                if (targetTag === "LI" && parent === search) {
                    // 点击的是搜索出来的LI:把选中的内容插入到文本域中话题位置
                    inpText.value = inpText.value.replace(reg, `#${target.innerHTML} `);
                    prev = target.innerHTML;
                    search.innerHTML = '';
                    search.style.display = 'none';
                    return;
                }
                if (targetTag === 'TEXTAREA' && target === inpText) {
                    // 点击的是文本域:啥都不处理
                    return;
                }
                // 剩下情况都是让搜索区域消失
                search.innerHTML = '';
                search.style.display = 'none';
            });

            /* 文本框输入中 */
            inpText.addEventListener('input', throttle(function () {
                let val = inpText.value,
                    match = reg.exec(val);
                match = match ? match[1] : '';
                if (match !== '' && match !== prev) {
                    // 用户输入了话题
                    search.style.display = 'block';
                    prev = match;
                    queryData(match);
                    return;
                }
                // 用户没有输入话题
                search.style.display = 'none';
            }));

            /* 插入话题 */
            talk.addEventListener('click', debounce(function () {
                let val = inpText.value,
                    n,
                    m;
                if (reg.test(val)) {
                    alert('当前已经插入过相关话题了~');
                    return;
                }
                n = inpText.selectionStart;
                m = inpText.selectionEnd;
                inpText.value = `${val.slice(0, n)}# ${val.slice(m)}`;
                inpText.focus();
                inpText.setSelectionRange(n + 1, m + 1);
            }));

            /* 话题解析 */
            upload.addEventListener('click', debounce(function () {
                let val = inpText.value.trim(),
                    match = reg.exec(val),
                    res = [];
                match = match ? match[0] : '';
                if (val.length === 0) {
                    alert('请先输入内容~');
                    return;
                }
                if (match === '') {
                    // 没有发布话题
                    res.push({
                        type: 'text',
                        value: val.trim()
                    });
                } else {
                    // 有发布话题
                    let [$left, $right] = val.split(match);
                    $left = $left.trim();
                    $right = $right.trim();
                    if ($left) {
                        res.push({
                            type: 'text',
                            value: $left
                        });
                    }
                    res.push({
                        type: 'talk',
                        value: match
                    });
                    if ($right) {
                        res.push({
                            type: 'text',
                            value: $right
                        });
                    }
                }
                alert(JSON.stringify(res));
            }));
        })();
    </script>
</body>

</html>

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值