如何在URL中动态添加锚标记并滚动到URL中的标题

时间:2019-07-15 18:55:35

标签: jquery scroll url-rewriting anchor

我正在尝试使用jQuery构建功能,该功能将根据标头部分动态创建哈希锚链接。现在,如果有一个带有此哈希的URL,那么我想在页面加载时滚动到该部分。问题在于页面加载,浏览器将滚动到正确的锚点,然后跳回到顶部。这样就可以了,但不会持久。

我知道这里的问题是页面加载时锚标记不存在,所以我必须等待添加元素后才能执行滚动。

我试图做的是:

  • 缓存哈希和URL
  • 获取哈希
  • 从URL(replaceState)中删除
  • 使用缓存的哈希值滚动到该部分
  • 再次使用replaceState将URL返回到其原始阶段
var HeaderAnchors = function(el){

    //setup some variables
    this.$el        = $(el);
    this.$headers   = this.$el.find( 'h2, h3' );
    this.headerSlugs= [];
    this.urlHash    = window.location.hash;
    this.url        = window.location.href;

    this.init();

};

HeaderAnchors.prototype.init = function(){

    new Promise( (resolve, reject) => {

        this.createAnchors();

        return resolve();

    } )
    .then( ()=>{

        //check if there is hash and if the hash is in the array of headings
        if( this.urlHash && this.headerSlugs.includes( this.urlHash.substring(1) ) ){

            //disable the anchor from jumping
            history.replaceState("", document.title, window.location.pathname);

            //smooth scroll to the stop of the hash, which has the same ID as the hash
            Foundation.SmoothScroll.scrollToLoc( this.urlHash, {
                threshold: 50,
                offset: 100,
                animationDuration: 200
            }, ()=>{

                //add the url back to
                window.history.replaceState({ path: this.url }, '', this.url);

            });

        }

    } );


};

HeaderAnchors.prototype.createAnchors = function(){

    this.$headers.each( ( index, val )=>{

        const heading       = $(val);
        const headingText   = heading
            .text()
            .toLowerCase()
            .trim()
            .replace( /[^\w\s]/gi, '' )
            .replace(/ +/g,'-');

        this.headerSlugs.push( headingText );

        let hashIcon = $('<span />')
            .addClass( 'fas fa-hashtag' )
            .attr({
                'aria-hidden' : 'true'
            });

        let anchor = $('<a />')
            .attr({
                'href'  : `#${headingText}`,
                'class' : 'c-anchor-hash__link'

            })
            .append( hashIcon );

        heading
            .addClass( 'c-anchor-hash' )
            .attr( { 'id'    : headingText} )
            .prepend( anchor );

    } );
};

我希望锚点滚动到动态创建的锚点...并且确实如此,但是随后它会跳起来...

任何帮助都会很棒。谢谢!

1 个答案:

答案 0 :(得分:0)

解决问题的方法很简单。你太过分了。 只需这样做:

@RestController
public class UserController {

    private final ReactiveUserDetailsServiceImpl userService;

    private final TokenProvider jwtTokenUtil;

    private final JWTReactiveAuthenticationManager authenticationManager;

    ...

    @PostMapping(value="authorize")
    public Mono<JWTToken> authorize(LoginUser loginVM) {
        if (loginVM ==null || loginVM.getUsername().isEmpty() || loginVM.getPassword().isEmpty()) {
            return Mono.error(new RuntimeException("Bad request"));
        }

        Authentication authenticationToken =
                new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());

        Mono<Authentication> authentication = this.authenticationManager.authenticate(authenticationToken);
        authentication.doOnError(throwable -> {
            throw new BadCredentialsException("Bad crendentials");
        });
        ReactiveSecurityContextHolder.withAuthentication(authenticationToken);

        return authentication.map(auth -> {
            String jwt = jwtTokenUtil.createToken(auth);
            return new JWTToken(jwt);
        });
    }
}

@Service
public class ReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService, UserService {

    @Autowired
    private  UserRepository userRepository;

    @Override
    public Mono<UserDetails> findByUsername(String username) {

        return userRepository.findByUsername(username)
                .filter(Objects::nonNull)
                .switchIfEmpty(Mono.error(new BadCredentialsException(String.format("User %s not found in database", username))))
                .map(this::createSpringSecurityUser);

    }

    private org.springframework.security.core.userdetails.User createSpringSecurityUser(User user) {

        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

        SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());

        grantedAuthorities.add(grantedAuthority);

        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(), grantedAuthorities);
    }

    public Flux<User> findAll(){
        return userRepository.findAll();
    }

}   


@Component
public class TokenProvider {

    private final long validityInMilliseconds = 3600000; // 1h

    private static final String SALT_KEY = "123";

    private String secretKey;

    private final Base64.Encoder encoder = Base64.getEncoder();

    private static final String AUTHORITIES_KEY = "auth";

    private static final Logger logger = LogManager.getLogger(TokenProvider.class);

    @PostConstruct
    public void init() {
        this.secretKey = encoder.encodeToString(SALT_KEY.getBytes(StandardCharsets.UTF_8));

    }

    public String createToken(Authentication authentication) {

        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        long now = (new Date()).getTime();
        Date validity = new Date(now + this.validityInMilliseconds);

        return Jwts.builder()
                .setSubject(authentication.getName())
                .claim(AUTHORITIES_KEY, authorities)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .setExpiration(validity)
                .compact();

    }

    public Authentication getAuthentication(String token) {
        if (StringUtils.isEmpty(token) || !validateToken(token)) {
            throw new BadCredentialsException("Invalid token");
        }

        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();

        Collection<? extends GrantedAuthority> authorities
                = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, token, authorities);

    }

    private boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.info("Invalid JWT signature.");
            logger.trace("Invalid JWT signature trace: {}", e);
        } catch (MalformedJwtException e) {
            logger.info("Invalid JWT token.");
            logger.trace("Invalid JWT token trace: {}", e);
        } catch (ExpiredJwtException e) {
            logger.info("Expired JWT token.");
            logger.trace("Expired JWT token trace: {}", e);
        } catch (UnsupportedJwtException e) {
            logger.info("Unsupported JWT token.");
            logger.trace("Unsupported JWT token trace: {}", e);
        } catch (IllegalArgumentException e) {
            logger.info("JWT token compact of handler are invalid.");
            logger.trace("JWT token compact of handler are invalid trace: {}", e);
        }
        return false;
    }

}

public class JWTReactiveAuthenticationManager implements ReactiveAuthenticationManager {

    private final ReactiveUserDetailsServiceImpl userService;

    private final PasswordEncoder passwordEncoder;

    private static final Logger logger = LogManager.getLogger(JWTReactiveAuthenticationManager.class);

    public JWTReactiveAuthenticationManager(final PasswordEncoder passwordEncoder, final ReactiveUserDetailsServiceImpl userService) {
        this.passwordEncoder = passwordEncoder;
        this.userService = userService;
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {

        if (authentication.isAuthenticated()) {
            return Mono.just(authentication);
        }

        return Mono.just(authentication)
                .switchIfEmpty(Mono.defer(this::raiseBadCredentials))
                .cast(UsernamePasswordAuthenticationToken.class)
                .flatMap(this::authenticateToken)
                .publishOn(Schedulers.parallel())
                .onErrorResume(e -> raiseBadCredentials())
                .filter(u -> passwordEncoder.matches((String) authentication.getCredentials(), u.getPassword()))
                .switchIfEmpty(Mono.defer(this::raiseBadCredentials))
                .map(u -> new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), u.getAuthorities()));

    }

    private <T> Mono<T> raiseBadCredentials() {
        return Mono.error(new BadCredentialsException("Invalid Credentials"));
    }

    private Mono<UserDetails> authenticateToken(final UsernamePasswordAuthenticationToken authenticationToken) {
        String username = authenticationToken.getName();

        logger.info("checking authentication for user " + username);

        //todo change, it's not reactive
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            logger.info("authenticated user " + username + ", setting security context");
            return userService.findByUsername(username);
        }

        return null;
    }

}

@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SpringSecurityWebFluxConfig {

    private ReactiveUserDetailsServiceImpl userService;

    private TokenProvider tokenUtil;

    private static final String[] AUTH_WHITELIST = {
        "/resources/**",
        "/webjars/**",
        "/authorize/**",
        "/favicon.ico",};

    public SpringSecurityWebFluxConfig(TokenProvider tokenUtil, ReactiveUserDetailsServiceImpl userService) {
        this.tokenUtil = tokenUtil;
        this.userService = userService;
    }

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, UnauthorizedAuthenticationEntryPoint entryPoint) {

        http.httpBasic().disable()
                .formLogin().disable()
                .csrf().disable()
                .logout().disable();

        http
                .exceptionHandling()
                .authenticationEntryPoint(entryPoint)
                .and()
                .authorizeExchange()
                .pathMatchers(HttpMethod.OPTIONS)
                    .permitAll()
                .pathMatchers("/users")
                    .permitAll()
                .and()
                .addFilterAt(webFilter(), SecurityWebFiltersOrder.AUTHORIZATION)
                .authorizeExchange()
                .pathMatchers(AUTH_WHITELIST).permitAll()
                .anyExchange().authenticated();

        return http.build();

    }

    @Bean
    public AuthenticationWebFilter webFilter() {
        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(repositoryReactiveAuthenticationManager());
        authenticationWebFilter.setAuthenticationConverter(new TokenAuthenticationConverter(tokenUtil));
        authenticationWebFilter.setRequiresAuthenticationMatcher(new JWTHeadersExchangeMatcher());
        authenticationWebFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
        return authenticationWebFilter;
    }

    @Bean
    public JWTReactiveAuthenticationManager repositoryReactiveAuthenticationManager() {
        JWTReactiveAuthenticationManager repositoryReactiveAuthenticationManager = new JWTReactiveAuthenticationManager(passwordEncoder(), userService);
        return repositoryReactiveAuthenticationManager;
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
$(document).ready(function() {
  $("#gen").click(function() {
    $("#HTMLdy").html('<a href="https://ess.com.ng">Click Me</a>');
  });
});