You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
23 KiB
564 lines
23 KiB
<!DOCTYPE html> |
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us"> |
|
<head> |
|
<title> |
|
Live Search With HUGO // Hagfi.sh |
|
</title> |
|
|
|
<link href="http://gmpg.org/xfn/11" rel="profile"> |
|
<meta http-equiv="content-type" content="text/html; charset=utf-8"> |
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1"> |
|
|
|
<meta name="description" content=""> |
|
<meta name="keywords" content=""> |
|
<meta name="author" content="Kristof Vandam"> |
|
<meta name="generator" content="Hugo 0.92.0" /> |
|
|
|
<meta property="og:title" content="Live Search With HUGO" /> |
|
<meta property="og:description" content="" /> |
|
<meta property="og:type" content="website" /> |
|
<meta property="og:locale" content="en_US" /> |
|
<meta property="og:url" content="https://blog.hagfi.sh/development/live-search-with-hugo/" /> |
|
|
|
|
|
|
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.5.0/base-min.css"> |
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.5.0/pure-min.css"> |
|
|
|
|
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.5.0/grids-responsive-min.css"> |
|
|
|
|
|
|
|
<link rel="stylesheet" href="https://blog.hagfi.sh//css/redlounge.css"> |
|
<link rel="stylesheet" href="https://blog.hagfi.sh//css/prism.css"> |
|
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet"> |
|
<link href='//fonts.googleapis.com/css?family=Raleway:400,200,100,700,300,500,600,800' rel='stylesheet' type='text/css'> |
|
<link href='//fonts.googleapis.com/css?family=Libre+Baskerville:400,700,400italic' rel='stylesheet' type='text/css'> |
|
|
|
|
|
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/touch-icon-144-precomposed.png"> |
|
<link rel="shortcut icon" type="image/x-icon" href="/img/favicon.png"> |
|
|
|
|
|
<link href="" rel="alternate" type="application/rss+xml" title="Hagfi.sh" /> |
|
|
|
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/fuse.js/3.2.1/fuse.min.js"></script> |
|
<script src="//cdn.jsdelivr.net/npm/vuewordcloud@18.7.11/VueWordCloud.js"></script> |
|
<script src="https://blog.hagfi.sh//js/prism.js"></script> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script type="application/javascript"> |
|
var doNotTrack = false; |
|
if (!doNotTrack) { |
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); |
|
ga('create', 'UA-124890410-1', 'auto'); |
|
|
|
ga('send', 'pageview'); |
|
} |
|
</script> |
|
|
|
|
|
</head> |
|
|
|
<body> |
|
|
|
|
|
<div id="layout" class="pure-g"> |
|
<div class="sidebar pure-u-1 pure-u-md-1-4" id="app"> |
|
<div class="header"> |
|
|
|
|
|
|
|
|
|
<h1 class="brand-title"><a href="/">Hagfi.sh</a></h1> |
|
<h2 class="brand-tagline">A devops guide to the galaxy</h2> |
|
|
|
<div class="counters"> |
|
<a class="counter" href="/">13 |
|
<div class="counter-sub">Documents</div> |
|
</a> |
|
<a class="counter" href="/tags">29 |
|
<div class="counter-sub">Tags</div> |
|
</a> |
|
<a class="counter" href="/categories">3 |
|
<div class="counter-sub">Categories</div> |
|
</a> |
|
</div> |
|
|
|
<nav class="nav"> |
|
|
|
</nav> |
|
|
|
<div class="search-wrapper"> |
|
<input |
|
type="text" |
|
placeholder="Search ..." |
|
v-model="search" |
|
@keydown.down.prevent="navigate(1)" |
|
@keydown.up.prevent="navigate(-1)" |
|
@keyup.enter.prevent="navigate(result[selected].href)" |
|
ref="searchInput" |
|
class="search" |
|
/> |
|
|
|
<svg height="100" width="100" ref="resultPoint" class="result-point"> |
|
<circle cx="5" cy="5" r="5" fill="#FFF" /> |
|
</svg> |
|
|
|
<ul class="result-items"> |
|
<li v-for="r, i of result" class="result-item" ref="resultItem"> |
|
<div class="result-item-wrapper" :class="{ 'result-item-selected': selected === i }"> |
|
<div class="result-item-left"> |
|
<span class="post-date"> |
|
<span class="post-date-day"><sup v-text="moment(r.date).format('D')"></sup></span><span class="post-date-separator" v-text="'/'"></span><span class="post-date-month" v-text="moment(r.date).format('MMM')"></span> <span class="post-date-year" v-text="moment(r.date).format('YYYY')"></span> |
|
</span> |
|
<template v-if="r.author">By <a class="post-author" v-text="r.author"></a></template> |
|
</div> |
|
<div class="result-item-left"> |
|
<span class="result-item-separator nav-item-separator" v-text="'//'"></span><a :href="r.href" v-text="r.title" class="result-item-link"></a> |
|
</div> |
|
</div> |
|
</li> |
|
</ul> |
|
</div> |
|
|
|
|
|
|
|
</div> |
|
</div> |
|
|
|
|
|
|
|
|
|
<div class="content pure-u-1 pure-u-md-3-4"> |
|
<a name="top"></a> |
|
|
|
|
|
|
|
|
|
<div id="toc" class="pure-u-1 pure-u-md-1-4"> |
|
<small class="toc-label">Contents</small> |
|
<nav id="TableOfContents"> |
|
<ul> |
|
<li><a href="#research">Research</a></li> |
|
<li><a href="#create-a-json-object-containing-all-articles">Create a JSON object containing all articles</a></li> |
|
<li><a href="#add-the-required-dependencies-we-use-cdns">Add the required dependencies (we use CDN’s)</a></li> |
|
<li><a href="#add-the-actual-search-logic">Add the actual search logic</a> |
|
<ul> |
|
<li><a href="#create-the-vue-instance">Create the Vue instance</a></li> |
|
<li><a href="#create-a-data-object">Create a data object</a></li> |
|
<li><a href="#what-todo-when-everything-is-ready">What todo when everything is ready</a></li> |
|
<li><a href="#when-something-is-entered-inside-the-search-field">When something is entered inside the search field</a></li> |
|
<li><a href="#ok-cool-now-how-do-i-showcase-the-results">Ok, cool, now how do I showcase the results?</a></li> |
|
</ul> |
|
</li> |
|
</ul> |
|
</nav> |
|
</div> |
|
|
|
|
|
<section class="post"> |
|
<h1 class="post-title"> |
|
<a href="/development/live-search-with-hugo/">Live Search With HUGO</a> |
|
</h1> |
|
<h3 class="post-subtitle"> |
|
|
|
</h3> |
|
|
|
<span class="post-date"> |
|
<span class="post-date-day"><sup>29</sup></span><span class="post-date-separator">/</span><span class="post-date-month">Aug</span> <span class="post-date-year">2018</span> |
|
</span> |
|
|
|
|
|
|
|
<span class="post-author-single">By <a class="post-author" target="">Kristof Vandam</a></span> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="post-categories"> |
|
|
|
<a class="post-category post-category-development" href="https://blog.hagfi.sh//categories/development">development</a> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
<p>HUGO is static, that’s a fact. How can I implement a live search? Searching the internet provided me only solutions |
|
that require a page refresh, this time of age performance is key, so that’s why I wanted a fast and fuzzy search implementation.</p> |
|
<h2 id="research">Research</h2> |
|
<p>Some this I found which helped to get there are:</p> |
|
<ul> |
|
<li><a href="https://gohugo.io/tools/search/">here</a> <em>more specific</em> <a href="https://gist.github.com/eddiewebb/735feb48f50f0ddd65ae5606a1cb41ae">here</a></li> |
|
<li><a href="https://vuejs.org/">https://vuejs.org/</a></li> |
|
<li><a href="http://fusejs.io/">http://fusejs.io/</a></li> |
|
<li><a href="https://momentjs.com/">https://momentjs.com/</a></li> |
|
<li><a href="https://github.com/axios/axios">https://github.com/axios/axios</a></li> |
|
</ul> |
|
<h2 id="create-a-json-object-containing-all-articles">Create a JSON object containing all articles</h2> |
|
<p>Actually every data you want to search, in this guide (and on this website) I use the following data:</p> |
|
<ol> |
|
<li>Title</li> |
|
<li>Date</li> |
|
<li>Author</li> |
|
<li>Tags</li> |
|
<li>Content</li> |
|
</ol> |
|
<p>This is specified in a custom <em>layout</em>. Note the <code>(dict "title" ...)</code> line. You can add any data that HUGO processes (for each article). Its a list of key/values, the keys are presented between the quotes, the value as first value.</p> |
|
<p><strong>layouts/json/single.html</strong></p> |
|
<pre tabindex="0"><code class="language-.language-none.line-numbers" data-lang=".language-none.line-numbers">{{- $.Scratch.Add "index" slice -}} |
|
{{- range where .Site.Pages "Type" "not in" (slice "page" "json") -}} |
|
{{- $.Scratch.Add "index" (dict "title" .Title "date" .Date "author" .Params.author "href" .Permalink "tags" .Params.tags "content" .Plain) -}} |
|
{{- end -}} |
|
{{- $.Scratch.Get "index" | jsonify -}} |
|
</code></pre><p>Now, with this file in place the next thing to do is to create a content page, where this layout is used. This file triggers the creation of “index.json”.</p> |
|
<p><strong>content/search.md</strong></p> |
|
<pre tabindex="0"><code class="language-.language-yaml.line-numbers" data-lang=".language-yaml.line-numbers">--- |
|
date: "2017-03-05T21:10:52+01:00" |
|
type: "json" |
|
url: "index.json" |
|
--- |
|
</code></pre><p><strong>Example of the data returned</strong> |
|
<em>You can checkout the json object for this website, just go to</em> <a href="https://hagfi.sh/index.json">https://hagfi.sh/index.json</a></p> |
|
<pre tabindex="0"><code class="language-.language-json.line-numbers" data-lang=".language-json.line-numbers">[ |
|
{ |
|
"author": "Kristof Vandam", |
|
"content": "HUGO is static, that\u0026rsquo;s a fact. How can I implement a live search? Searching the internet provided me only solutions that require a page refresh, this time of age performance is key, so that\u0026rsquo;s why I wanted a fast and fuzzy search implementation. Research Some this I found which helped to get there are:\n https://gohugo.io/tools/search/ ", |
|
"date": "2018-08-29T22:44:46+02:00", |
|
"href": "http://localhost:1313/development/live-search-with-hugo/", |
|
"tags": null, |
|
"title": "Live Search With HUGO" |
|
} |
|
] |
|
</code></pre><h2 id="add-the-required-dependencies-we-use-cdns">Add the required dependencies (we use CDN’s)</h2> |
|
<p>Make sure the following dependencies are loaded between the head tags. We use a little trick to let the browser decide if http or https is used. These are called <em>Protocol-Relative URL’s</em>.</p> |
|
<pre tabindex="0"><code class="language-.language-markup.line-numbers" data-lang=".language-markup.line-numbers"><script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script> |
|
<script src="//cdn.bootcss.com/fuse.js/3.2.0/fuse.min.js"></script> |
|
</code></pre><h2 id="add-the-actual-search-logic">Add the actual search logic</h2> |
|
<p>It’s a best practice to add the JavaScript right before the closing body tags. I highly suggest checking out VueJS with Webpack, but in this case a some simple JS inside script tags will do just fine.</p> |
|
<p>I will go over each section to clarify.</p> |
|
<pre tabindex="0"><code class="language-.language-javascript.line-numbers" data-lang=".language-javascript.line-numbers">var app = new Vue({ |
|
el: '#app', |
|
data: { |
|
fuse: null, |
|
search: "", |
|
result: [], |
|
index: [] |
|
}, |
|
mounted() { |
|
|
|
let self = this |
|
|
|
let options = { |
|
shouldSort: true, |
|
threshold: 0.6, |
|
location: 0, |
|
distance: 100, |
|
maxPatternLength: 32, |
|
minMatchCharLength: 1, |
|
keys: [ |
|
"title", |
|
"author", |
|
"date", |
|
"content" |
|
] |
|
} |
|
axios.get('/index.json') |
|
.then(function (response) { |
|
self.index = response.data |
|
self.fuse = new Fuse(response.data, options); |
|
self.result = fuse.search(""); |
|
}) |
|
.catch(function (error) { |
|
console.log(error) |
|
}) |
|
}, |
|
watch: { |
|
search(nval, oval) { |
|
if (nval.length > 0) { |
|
this.result = this.fuse.search(nval) |
|
} else { |
|
this.result = [] |
|
} |
|
} |
|
} |
|
}) |
|
</code></pre><h3 id="create-the-vue-instance">Create the Vue instance</h3> |
|
<p>When creating a new Vue instance we assign Vue to a DOM element, most of the time an ID on your body tag is used.</p> |
|
<pre tabindex="0"><code class="language-.language-javascript.line-numbers" data-lang=".language-javascript.line-numbers">var app = new Vue({ |
|
el: '#app', |
|
... |
|
}) |
|
</code></pre><h3 id="create-a-data-object">Create a data object</h3> |
|
<p>This object is accesible across your DOM and Vue instance. Inside functions you can reffer to these with <code>this.*</code>.language-<br> |
|
I initiated some variables like ‘fuse’ so it can be used inside <em>watch</em> and <em>methods</em>.</p> |
|
<pre tabindex="0"><code class="language-.language-javascript.line-numbers" data-lang=".language-javascript.line-numbers">data: { |
|
fuse: null, |
|
search: "", |
|
result: [], |
|
index: [] |
|
}, |
|
</code></pre><h3 id="what-todo-when-everything-is-ready">What todo when everything is ready</h3> |
|
<p>The <code>mounted()</code> function is triggered when everything ready to start processing your custom code. <em>(This function used to name ‘ready()')</em>.<br> |
|
We assign <code>this</code> to <code>self</code> to handle some scope issues in the axios promise.<br> |
|
We polulate some options for FuseJS, note that the keys array is important here. Here we specify which keys of our index.json we want to search.<br> |
|
The index.json file is loaded with AJAX, this way the page should not wait for content that is not required immediately.<br> |
|
When axios retrieves the date we create a Fuse instance (assigned to <code>self.fuse</code> (or <code>this.fuse</code>)).</p> |
|
<pre tabindex="0"><code class="language-.language-javascript.line-numbers" data-lang=".language-javascript.line-numbers">mounted() { |
|
|
|
let self = this |
|
|
|
let options = { |
|
shouldSort: true, |
|
threshold: 0.6, |
|
location: 0, |
|
distance: 100, |
|
maxPatternLength: 32, |
|
minMatchCharLength: 1, |
|
keys: [ |
|
"title", |
|
"author", |
|
"date", |
|
"content" |
|
] |
|
} |
|
axios.get('/index.json') |
|
.then(function (response) { |
|
self.index = response.data |
|
self.fuse = new Fuse(response.data, options); |
|
self.result = fuse.search(""); |
|
}) |
|
.catch(function (error) { |
|
console.log(error) |
|
}) |
|
}, |
|
</code></pre><h3 id="when-something-is-entered-inside-the-search-field">When something is entered inside the search field</h3> |
|
<p>We watch for <code>this.search</code> to change, if it changes this function is called. Remember we set <code>search: ""</code> inside our data object? |
|
If the ‘nval’ (New VALue) is larger than 0 characters we trigger the search function of fuse, which will return a new data set, but filtered. |
|
This dataset is stored inside <code>this.result</code>.language-</p> |
|
<p>If the length of ‘nval’ changes to 0 characters we hardcode the result to be an empty array (to prevent possible edgecases).</p> |
|
<pre tabindex="0"><code class="language-.language-javascript.line-numbers" data-lang=".language-javascript.line-numbers">watch: { |
|
search(nval, oval) { |
|
if (nval.length > 0) { |
|
this.result = this.fuse.search(nval) |
|
} else { |
|
this.result = [] |
|
} |
|
} |
|
} |
|
</code></pre><h3 id="ok-cool-now-how-do-i-showcase-the-results">Ok, cool, now how do I showcase the results?</h3> |
|
<p>Well, it’s up to you. The most important parts in this example are:</p> |
|
<ol> |
|
<li>Bind <code>this.search</code> to the input field (with <code>v-model</code>)</li> |
|
<li>Loop through <code>this.result</code> with <code>v-for</code>, it will recreate the li tag ‘for each’ result item.</li> |
|
<li>Use the result item, reffered as <code>r</code>.</li> |
|
<li>Links are extracted from the result item by the ‘href’ key and bound to the href attribute. <code>:href="r.href"</code></li> |
|
</ol> |
|
<p>We use Moment.js to format the default (can be changed) HUGO date format to ‘D’ (Day), ‘MMM’ (Month, max 3 characters), ‘YYYY’ (Full Year).</p> |
|
<pre tabindex="0"><code class="language-.language-markup.line-numbers" data-lang=".language-markup.line-numbers"><div class="search-wrapper"> |
|
<input type="text" placeholder="Search ..." v-model="search" class="search"/> |
|
<ul class="result-items"> |
|
<li v-for="r of result" class="result-item"> |
|
<div class="result-item-wrapper"> |
|
<div class="result-item-left"> |
|
<span class="post-date"> |
|
<span class="post-date-day"><sup v-text="moment(r.date).format('D')"></sup></span><span class="post-date-separator">/</span><span class="post-date-month" v-text="moment(r.date).format('MMM')"></span> <span class="post-date-year" v-text="moment(r.date).format('YYYY')"></span> |
|
</span> |
|
<template v-if="r.author">By <a class="post-author" v-text="r.author"></a></template> |
|
</div> |
|
<div class="result-item-left"> |
|
<span class="nav-item-separator">//</span><a :href="r.href" v-text="r.title"></a> |
|
</div> |
|
</div> |
|
</li> |
|
</ul> |
|
</div> |
|
</code></pre> |
|
|
|
|
|
|
|
|
|
<div class="tags-list"> |
|
<span class="dark-red">Tags</span><span class="decorative-marker">//</span> |
|
|
|
<a class="post-tag post-tag-vue" href="https://blog.hagfi.sh//tags/vue">vue</a>, |
|
|
|
<a class="post-tag post-tag-vuejs" href="https://blog.hagfi.sh//tags/vuejs">vuejs</a>, |
|
|
|
<a class="post-tag post-tag-prism" href="https://blog.hagfi.sh//tags/prism">prism</a>, |
|
|
|
<a class="post-tag post-tag-prismjs" href="https://blog.hagfi.sh//tags/prismjs">prismjs</a>, |
|
|
|
<a class="post-tag post-tag-hugo" href="https://blog.hagfi.sh//tags/hugo">hugo</a>, |
|
|
|
<a class="post-tag post-tag-javascript" href="https://blog.hagfi.sh//tags/javascript">javascript</a>, |
|
|
|
<a class="post-tag post-tag-js" href="https://blog.hagfi.sh//tags/js">js</a>, |
|
|
|
<a class="post-tag post-tag-json" href="https://blog.hagfi.sh//tags/json">json</a>, |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="paging"> |
|
<span class="paging-label">More Reading</span> |
|
|
|
<div class="paging-newer"> |
|
<span class="dark-red">Newer</span><span class="decorative-marker">//</span> |
|
<a class="paging-link" href="/administration/letsencrypt/">Let's Encrypt</a> |
|
</div> |
|
|
|
|
|
|
|
<div class="paging-older"> |
|
<span class="dark-red">Older</span><span class="decorative-marker">//</span> |
|
<a class="paging-link" href="/tools/ncdu/">NCDU: NCurses Disk Usage</a> |
|
</div> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
<div id="disqus_thread"></div> |
|
<script type="application/javascript"> |
|
var disqus_config = function () { |
|
|
|
|
|
|
|
}; |
|
(function() { |
|
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) { |
|
document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.'; |
|
return; |
|
} |
|
var d = document, s = d.createElement('script'); s.async = true; |
|
s.src = '//' + "hagfish" + '.disqus.com/embed.js'; |
|
s.setAttribute('data-timestamp', +new Date()); |
|
(d.head || d.body).appendChild(s); |
|
})(); |
|
</script> |
|
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript> |
|
<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a> |
|
|
|
|
|
<div class="footer"> |
|
<hr class="thin" /> |
|
<div class="pure-menu pure-menu-horizontal pure-menu-open"> |
|
<ul class="footer-menu"> |
|
|
|
</ul> |
|
</div> |
|
|
|
<p>© 2022. All rights reserved.</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
|
|
|
|
<script> |
|
var app = new Vue({ |
|
el: '#app', |
|
data: { |
|
fuse: null, |
|
search: "", |
|
result: [], |
|
index: [], |
|
selected: 0 |
|
}, |
|
mounted() { |
|
let self = this |
|
|
|
|
|
window.addEventListener("keypress", function(e) { |
|
self.$refs.searchInput.focus() |
|
}) |
|
|
|
let options = { |
|
shouldSort: true, |
|
threshold: 0.6, |
|
location: 0, |
|
distance: 100, |
|
maxPatternLength: 32, |
|
minMatchCharLength: 1, |
|
keys: [ |
|
"title", |
|
"author", |
|
"date", |
|
"content" |
|
] |
|
} |
|
axios.get('/index.json') |
|
.then(function (response) { |
|
self.index = response.data |
|
self.fuse = new Fuse(response.data, options) |
|
}) |
|
.catch(function (error) { |
|
}) |
|
}, |
|
watch: { |
|
result(nval, oval) { |
|
nval.length > 0 ? this.pointer(0) : this.pointer(-1) |
|
}, |
|
search(nval, oval) { |
|
this.result = this.fuse.search(nval) |
|
} |
|
}, |
|
methods: { |
|
navigate(val) { |
|
switch (val) { |
|
case 1: if (this.selected < this.result.length - 1) { this.selected++ }; break; |
|
case -1: if (this.selected > 0 ) { this.selected-- }; break; |
|
default: window.location.href = val; break; |
|
} |
|
this.pointer(this.selected) |
|
}, |
|
pointer(selected) { |
|
let self = this |
|
|
|
if (selected >= 0) { |
|
Vue.nextTick().then(function() { |
|
let height = self.$refs.resultItem[selected].clientHeight |
|
let top = self.$refs.resultItem[selected].getBoundingClientRect().top |
|
let left = self.$refs.resultItem[selected].getBoundingClientRect().left |
|
|
|
self.$refs.resultPoint.style.top = (top+height/2)+'px' |
|
self.$refs.resultPoint.style.left = (left-20)+'px' |
|
}) |
|
} else { |
|
this.$refs.resultPoint.style.left = '-50px' |
|
return |
|
} |
|
} |
|
} |
|
}) |
|
</script> |
|
</body> |
|
</html>
|
|
|